changeset: 101661:050e5f803999 parent: 101657:19488e23dcdb user: Serhiy Storchaka date: Fri Jun 03 21:42:55 2016 +0300 files: Lib/test/test_float.py Lib/test/test_getargs2.py Misc/NEWS Objects/abstract.c Objects/floatobject.c description: 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. diff -r 19488e23dcdb -r 050e5f803999 Lib/test/test_float.py --- a/Lib/test/test_float.py Fri Jun 03 10:48:43 2016 +0300 +++ b/Lib/test/test_float.py Fri Jun 03 21:42:55 2016 +0300 @@ -161,11 +161,12 @@ def __float__(self): return float(str(self)) + 1 - self.assertAlmostEqual(float(Foo1()), 42.) - self.assertAlmostEqual(float(Foo2()), 42.) - self.assertAlmostEqual(float(Foo3(21)), 42.) + self.assertEqual(float(Foo1()), 42.) + self.assertEqual(float(Foo2()), 42.) + with self.assertWarns(DeprecationWarning): + self.assertEqual(float(Foo3(21)), 42.) self.assertRaises(TypeError, float, Foo4(42)) - self.assertAlmostEqual(float(FooStr('8')), 9.) + self.assertEqual(float(FooStr('8')), 9.) class Foo5: def __float__(self): @@ -176,10 +177,14 @@ class F: def __float__(self): return OtherFloatSubclass(42.) - self.assertAlmostEqual(float(F()), 42.) - self.assertIs(type(float(F())), OtherFloatSubclass) - self.assertAlmostEqual(FloatSubclass(F()), 42.) - self.assertIs(type(FloatSubclass(F())), FloatSubclass) + with self.assertWarns(DeprecationWarning): + self.assertEqual(float(F()), 42.) + with self.assertWarns(DeprecationWarning): + 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): self.assertFalse((1.1).is_integer()) diff -r 19488e23dcdb -r 050e5f803999 Lib/test/test_getargs2.py --- a/Lib/test/test_getargs2.py Fri Jun 03 10:48:43 2016 +0300 +++ b/Lib/test/test_getargs2.py Fri Jun 03 21:42:55 2016 +0300 @@ -365,7 +365,8 @@ self.assertEqual(getargs_f(FloatSubclass(7.5)), 7.5) self.assertEqual(getargs_f(FloatSubclass2(7.5)), 7.5) 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) for x in (FLT_MIN, -FLT_MIN, FLT_MAX, -FLT_MAX, INF, -INF): @@ -390,7 +391,8 @@ self.assertEqual(getargs_d(FloatSubclass(7.5)), 7.5) self.assertEqual(getargs_d(FloatSubclass2(7.5)), 7.5) 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) for x in (DBL_MIN, -DBL_MIN, DBL_MAX, -DBL_MAX, INF, -INF): diff -r 19488e23dcdb -r 050e5f803999 Misc/NEWS --- a/Misc/NEWS Fri Jun 03 10:48:43 2016 +0300 +++ b/Misc/NEWS Fri Jun 03 21:42:55 2016 +0300 @@ -10,6 +10,11 @@ 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 instruction decoding. Based on patch by Demur Rumed. diff -r 19488e23dcdb -r 050e5f803999 Objects/abstract.c --- a/Objects/abstract.c Fri Jun 03 10:48:43 2016 +0300 +++ b/Objects/abstract.c Fri Jun 03 21:42:55 2016 +0300 @@ -1351,21 +1351,39 @@ if (o == NULL) return null_error(); + if (PyFloat_CheckExact(o)) { + Py_INCREF(o); + return o; + } m = o->ob_type->tp_as_number; if (m && m->nb_float) { /* This should include subclasses of float */ 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, - "__float__ returned non-float (type %.200s)", - res->ob_type->tp_name); + "%.50s.__float__ returned non-float (type %.50s)", + o->ob_type->tp_name, res->ob_type->tp_name); Py_DECREF(res); 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 */ - PyFloatObject *po = (PyFloatObject *)o; - return PyFloat_FromDouble(po->ob_fval); + return PyFloat_FromDouble(PyFloat_AS_DOUBLE(o)); } return PyFloat_FromString(o); } diff -r 19488e23dcdb -r 050e5f803999 Objects/floatobject.c --- a/Objects/floatobject.c Fri Jun 03 10:48:43 2016 +0300 +++ b/Objects/floatobject.c Fri Jun 03 21:42:55 2016 +0300 @@ -215,35 +215,49 @@ PyFloat_AsDouble(PyObject *op) { PyNumberMethods *nb; - PyFloatObject *fo; + PyObject *res; double val; - if (op && PyFloat_Check(op)) - return PyFloat_AS_DOUBLE((PyFloatObject*) op); - if (op == NULL) { PyErr_BadArgument(); return -1; } - if ((nb = Py_TYPE(op)->tp_as_number) == NULL || nb->nb_float == NULL) { - PyErr_SetString(PyExc_TypeError, "a float is required"); + if (PyFloat_Check(op)) { + 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; } - fo = (PyFloatObject*) (*nb->nb_float) (op); - if (fo == NULL) - return -1; - if (!PyFloat_Check(fo)) { - Py_DECREF(fo); - PyErr_SetString(PyExc_TypeError, - "nb_float should return float object"); + res = (*nb->nb_float) (op); + if (res == NULL) { 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); - Py_DECREF(fo); - + val = PyFloat_AS_DOUBLE(res); + Py_DECREF(res); return val; }