Issue #26983: float() now always return an instance of exact float.

The deprecation warning is emitted if __float__ returns an instance of
a strict subclass of float.  In a future versions of Python this can
be an error.
This commit is contained in:
Serhiy Storchaka 2016-06-03 21:42:55 +03:00
parent bb7f7327fe
commit 16931c3559
5 changed files with 76 additions and 32 deletions

View File

@ -161,11 +161,12 @@ class GeneralFloatCases(unittest.TestCase):
def __float__(self): def __float__(self):
return float(str(self)) + 1 return float(str(self)) + 1
self.assertAlmostEqual(float(Foo1()), 42.) self.assertEqual(float(Foo1()), 42.)
self.assertAlmostEqual(float(Foo2()), 42.) self.assertEqual(float(Foo2()), 42.)
self.assertAlmostEqual(float(Foo3(21)), 42.) with self.assertWarns(DeprecationWarning):
self.assertEqual(float(Foo3(21)), 42.)
self.assertRaises(TypeError, float, Foo4(42)) self.assertRaises(TypeError, float, Foo4(42))
self.assertAlmostEqual(float(FooStr('8')), 9.) self.assertEqual(float(FooStr('8')), 9.)
class Foo5: class Foo5:
def __float__(self): def __float__(self):
@ -176,10 +177,14 @@ class GeneralFloatCases(unittest.TestCase):
class F: class F:
def __float__(self): def __float__(self):
return OtherFloatSubclass(42.) return OtherFloatSubclass(42.)
self.assertAlmostEqual(float(F()), 42.) with self.assertWarns(DeprecationWarning):
self.assertIs(type(float(F())), OtherFloatSubclass) self.assertEqual(float(F()), 42.)
self.assertAlmostEqual(FloatSubclass(F()), 42.) with self.assertWarns(DeprecationWarning):
self.assertIs(type(FloatSubclass(F())), FloatSubclass) self.assertIs(type(float(F())), float)
with self.assertWarns(DeprecationWarning):
self.assertEqual(FloatSubclass(F()), 42.)
with self.assertWarns(DeprecationWarning):
self.assertIs(type(FloatSubclass(F())), FloatSubclass)
def test_is_integer(self): def test_is_integer(self):
self.assertFalse((1.1).is_integer()) self.assertFalse((1.1).is_integer())

View File

@ -365,7 +365,8 @@ class Float_TestCase(unittest.TestCase):
self.assertEqual(getargs_f(FloatSubclass(7.5)), 7.5) self.assertEqual(getargs_f(FloatSubclass(7.5)), 7.5)
self.assertEqual(getargs_f(FloatSubclass2(7.5)), 7.5) self.assertEqual(getargs_f(FloatSubclass2(7.5)), 7.5)
self.assertRaises(TypeError, getargs_f, BadFloat()) self.assertRaises(TypeError, getargs_f, BadFloat())
self.assertEqual(getargs_f(BadFloat2()), 4.25) with self.assertWarns(DeprecationWarning):
self.assertEqual(getargs_f(BadFloat2()), 4.25)
self.assertEqual(getargs_f(BadFloat3(7.5)), 7.5) self.assertEqual(getargs_f(BadFloat3(7.5)), 7.5)
for x in (FLT_MIN, -FLT_MIN, FLT_MAX, -FLT_MAX, INF, -INF): for x in (FLT_MIN, -FLT_MIN, FLT_MAX, -FLT_MAX, INF, -INF):
@ -390,7 +391,8 @@ class Float_TestCase(unittest.TestCase):
self.assertEqual(getargs_d(FloatSubclass(7.5)), 7.5) self.assertEqual(getargs_d(FloatSubclass(7.5)), 7.5)
self.assertEqual(getargs_d(FloatSubclass2(7.5)), 7.5) self.assertEqual(getargs_d(FloatSubclass2(7.5)), 7.5)
self.assertRaises(TypeError, getargs_d, BadFloat()) self.assertRaises(TypeError, getargs_d, BadFloat())
self.assertEqual(getargs_d(BadFloat2()), 4.25) with self.assertWarns(DeprecationWarning):
self.assertEqual(getargs_d(BadFloat2()), 4.25)
self.assertEqual(getargs_d(BadFloat3(7.5)), 7.5) self.assertEqual(getargs_d(BadFloat3(7.5)), 7.5)
for x in (DBL_MIN, -DBL_MIN, DBL_MAX, -DBL_MAX, INF, -INF): for x in (DBL_MIN, -DBL_MIN, DBL_MAX, -DBL_MAX, INF, -INF):

View File

@ -10,6 +10,11 @@ What's New in Python 3.6.0 alpha 2
Core and Builtins Core and Builtins
----------------- -----------------
- Issue #26983: float() now always return an instance of exact float.
The deprecation warning is emitted if __float__ returns an instance of
a strict subclass of float. In a future versions of Python this can
be an error.
- Issue #27097: Python interpreter is now about 7% faster due to optimized - Issue #27097: Python interpreter is now about 7% faster due to optimized
instruction decoding. Based on patch by Demur Rumed. instruction decoding. Based on patch by Demur Rumed.

View File

@ -1351,21 +1351,39 @@ PyNumber_Float(PyObject *o)
if (o == NULL) if (o == NULL)
return null_error(); return null_error();
if (PyFloat_CheckExact(o)) {
Py_INCREF(o);
return o;
}
m = o->ob_type->tp_as_number; m = o->ob_type->tp_as_number;
if (m && m->nb_float) { /* This should include subclasses of float */ if (m && m->nb_float) { /* This should include subclasses of float */
PyObject *res = m->nb_float(o); PyObject *res = m->nb_float(o);
if (res && !PyFloat_Check(res)) { double val;
if (!res || PyFloat_CheckExact(res)) {
return res;
}
if (!PyFloat_Check(res)) {
PyErr_Format(PyExc_TypeError, PyErr_Format(PyExc_TypeError,
"__float__ returned non-float (type %.200s)", "%.50s.__float__ returned non-float (type %.50s)",
res->ob_type->tp_name); o->ob_type->tp_name, res->ob_type->tp_name);
Py_DECREF(res); Py_DECREF(res);
return NULL; return NULL;
} }
return res; /* Issue #26983: warn if 'res' not of exact type float. */
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
"%.50s.__float__ returned non-float (type %.50s). "
"The ability to return an instance of a strict subclass of float "
"is deprecated, and may be removed in a future version of Python.",
o->ob_type->tp_name, res->ob_type->tp_name)) {
Py_DECREF(res);
return NULL;
}
val = PyFloat_AS_DOUBLE(res);
Py_DECREF(res);
return PyFloat_FromDouble(val);
} }
if (PyFloat_Check(o)) { /* A float subclass with nb_float == NULL */ if (PyFloat_Check(o)) { /* A float subclass with nb_float == NULL */
PyFloatObject *po = (PyFloatObject *)o; return PyFloat_FromDouble(PyFloat_AS_DOUBLE(o));
return PyFloat_FromDouble(po->ob_fval);
} }
return PyFloat_FromString(o); return PyFloat_FromString(o);
} }

View File

@ -215,35 +215,49 @@ double
PyFloat_AsDouble(PyObject *op) PyFloat_AsDouble(PyObject *op)
{ {
PyNumberMethods *nb; PyNumberMethods *nb;
PyFloatObject *fo; PyObject *res;
double val; double val;
if (op && PyFloat_Check(op))
return PyFloat_AS_DOUBLE((PyFloatObject*) op);
if (op == NULL) { if (op == NULL) {
PyErr_BadArgument(); PyErr_BadArgument();
return -1; return -1;
} }
if ((nb = Py_TYPE(op)->tp_as_number) == NULL || nb->nb_float == NULL) { if (PyFloat_Check(op)) {
PyErr_SetString(PyExc_TypeError, "a float is required"); return PyFloat_AS_DOUBLE(op);
}
nb = Py_TYPE(op)->tp_as_number;
if (nb == NULL || nb->nb_float == NULL) {
PyErr_Format(PyExc_TypeError, "must be real number, not %.50s",
op->ob_type->tp_name);
return -1; return -1;
} }
fo = (PyFloatObject*) (*nb->nb_float) (op); res = (*nb->nb_float) (op);
if (fo == NULL) if (res == NULL) {
return -1;
if (!PyFloat_Check(fo)) {
Py_DECREF(fo);
PyErr_SetString(PyExc_TypeError,
"nb_float should return float object");
return -1; return -1;
} }
if (!PyFloat_CheckExact(res)) {
if (!PyFloat_Check(res)) {
PyErr_Format(PyExc_TypeError,
"%.50s.__float__ returned non-float (type %.50s)",
op->ob_type->tp_name, res->ob_type->tp_name);
Py_DECREF(res);
return -1;
}
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
"%.50s.__float__ returned non-float (type %.50s). "
"The ability to return an instance of a strict subclass of float "
"is deprecated, and may be removed in a future version of Python.",
op->ob_type->tp_name, res->ob_type->tp_name)) {
Py_DECREF(res);
return -1;
}
}
val = PyFloat_AS_DOUBLE(fo); val = PyFloat_AS_DOUBLE(res);
Py_DECREF(fo); Py_DECREF(res);
return val; return val;
} }