Skip to content

Commit 16931c3

Browse files
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.
1 parent bb7f732 commit 16931c3

File tree

5 files changed

+76
-32
lines changed

5 files changed

+76
-32
lines changed

Lib/test/test_float.py

+13-8
Original file line numberDiff line numberDiff line change
@@ -161,11 +161,12 @@ class FooStr(str):
161161
def __float__(self):
162162
return float(str(self)) + 1
163163

164-
self.assertAlmostEqual(float(Foo1()), 42.)
165-
self.assertAlmostEqual(float(Foo2()), 42.)
166-
self.assertAlmostEqual(float(Foo3(21)), 42.)
164+
self.assertEqual(float(Foo1()), 42.)
165+
self.assertEqual(float(Foo2()), 42.)
166+
with self.assertWarns(DeprecationWarning):
167+
self.assertEqual(float(Foo3(21)), 42.)
167168
self.assertRaises(TypeError, float, Foo4(42))
168-
self.assertAlmostEqual(float(FooStr('8')), 9.)
169+
self.assertEqual(float(FooStr('8')), 9.)
169170

170171
class Foo5:
171172
def __float__(self):
@@ -176,10 +177,14 @@ def __float__(self):
176177
class F:
177178
def __float__(self):
178179
return OtherFloatSubclass(42.)
179-
self.assertAlmostEqual(float(F()), 42.)
180-
self.assertIs(type(float(F())), OtherFloatSubclass)
181-
self.assertAlmostEqual(FloatSubclass(F()), 42.)
182-
self.assertIs(type(FloatSubclass(F())), FloatSubclass)
180+
with self.assertWarns(DeprecationWarning):
181+
self.assertEqual(float(F()), 42.)
182+
with self.assertWarns(DeprecationWarning):
183+
self.assertIs(type(float(F())), float)
184+
with self.assertWarns(DeprecationWarning):
185+
self.assertEqual(FloatSubclass(F()), 42.)
186+
with self.assertWarns(DeprecationWarning):
187+
self.assertIs(type(FloatSubclass(F())), FloatSubclass)
183188

184189
def test_is_integer(self):
185190
self.assertFalse((1.1).is_integer())

Lib/test/test_getargs2.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,8 @@ def test_f(self):
365365
self.assertEqual(getargs_f(FloatSubclass(7.5)), 7.5)
366366
self.assertEqual(getargs_f(FloatSubclass2(7.5)), 7.5)
367367
self.assertRaises(TypeError, getargs_f, BadFloat())
368-
self.assertEqual(getargs_f(BadFloat2()), 4.25)
368+
with self.assertWarns(DeprecationWarning):
369+
self.assertEqual(getargs_f(BadFloat2()), 4.25)
369370
self.assertEqual(getargs_f(BadFloat3(7.5)), 7.5)
370371

371372
for x in (FLT_MIN, -FLT_MIN, FLT_MAX, -FLT_MAX, INF, -INF):
@@ -390,7 +391,8 @@ def test_d(self):
390391
self.assertEqual(getargs_d(FloatSubclass(7.5)), 7.5)
391392
self.assertEqual(getargs_d(FloatSubclass2(7.5)), 7.5)
392393
self.assertRaises(TypeError, getargs_d, BadFloat())
393-
self.assertEqual(getargs_d(BadFloat2()), 4.25)
394+
with self.assertWarns(DeprecationWarning):
395+
self.assertEqual(getargs_d(BadFloat2()), 4.25)
394396
self.assertEqual(getargs_d(BadFloat3(7.5)), 7.5)
395397

396398
for x in (DBL_MIN, -DBL_MIN, DBL_MAX, -DBL_MAX, INF, -INF):

Misc/NEWS

+5
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ What's New in Python 3.6.0 alpha 2
1010
Core and Builtins
1111
-----------------
1212

13+
- Issue #26983: float() now always return an instance of exact float.
14+
The deprecation warning is emitted if __float__ returns an instance of
15+
a strict subclass of float. In a future versions of Python this can
16+
be an error.
17+
1318
- Issue #27097: Python interpreter is now about 7% faster due to optimized
1419
instruction decoding. Based on patch by Demur Rumed.
1520

Objects/abstract.c

+24-6
Original file line numberDiff line numberDiff line change
@@ -1351,21 +1351,39 @@ PyNumber_Float(PyObject *o)
13511351

13521352
if (o == NULL)
13531353
return null_error();
1354+
if (PyFloat_CheckExact(o)) {
1355+
Py_INCREF(o);
1356+
return o;
1357+
}
13541358
m = o->ob_type->tp_as_number;
13551359
if (m && m->nb_float) { /* This should include subclasses of float */
13561360
PyObject *res = m->nb_float(o);
1357-
if (res && !PyFloat_Check(res)) {
1361+
double val;
1362+
if (!res || PyFloat_CheckExact(res)) {
1363+
return res;
1364+
}
1365+
if (!PyFloat_Check(res)) {
13581366
PyErr_Format(PyExc_TypeError,
1359-
"__float__ returned non-float (type %.200s)",
1360-
res->ob_type->tp_name);
1367+
"%.50s.__float__ returned non-float (type %.50s)",
1368+
o->ob_type->tp_name, res->ob_type->tp_name);
13611369
Py_DECREF(res);
13621370
return NULL;
13631371
}
1364-
return res;
1372+
/* Issue #26983: warn if 'res' not of exact type float. */
1373+
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
1374+
"%.50s.__float__ returned non-float (type %.50s). "
1375+
"The ability to return an instance of a strict subclass of float "
1376+
"is deprecated, and may be removed in a future version of Python.",
1377+
o->ob_type->tp_name, res->ob_type->tp_name)) {
1378+
Py_DECREF(res);
1379+
return NULL;
1380+
}
1381+
val = PyFloat_AS_DOUBLE(res);
1382+
Py_DECREF(res);
1383+
return PyFloat_FromDouble(val);
13651384
}
13661385
if (PyFloat_Check(o)) { /* A float subclass with nb_float == NULL */
1367-
PyFloatObject *po = (PyFloatObject *)o;
1368-
return PyFloat_FromDouble(po->ob_fval);
1386+
return PyFloat_FromDouble(PyFloat_AS_DOUBLE(o));
13691387
}
13701388
return PyFloat_FromString(o);
13711389
}

Objects/floatobject.c

+30-16
Original file line numberDiff line numberDiff line change
@@ -215,35 +215,49 @@ double
215215
PyFloat_AsDouble(PyObject *op)
216216
{
217217
PyNumberMethods *nb;
218-
PyFloatObject *fo;
218+
PyObject *res;
219219
double val;
220220

221-
if (op && PyFloat_Check(op))
222-
return PyFloat_AS_DOUBLE((PyFloatObject*) op);
223-
224221
if (op == NULL) {
225222
PyErr_BadArgument();
226223
return -1;
227224
}
228225

229-
if ((nb = Py_TYPE(op)->tp_as_number) == NULL || nb->nb_float == NULL) {
230-
PyErr_SetString(PyExc_TypeError, "a float is required");
231-
return -1;
226+
if (PyFloat_Check(op)) {
227+
return PyFloat_AS_DOUBLE(op);
232228
}
233229

234-
fo = (PyFloatObject*) (*nb->nb_float) (op);
235-
if (fo == NULL)
236-
return -1;
237-
if (!PyFloat_Check(fo)) {
238-
Py_DECREF(fo);
239-
PyErr_SetString(PyExc_TypeError,
240-
"nb_float should return float object");
230+
nb = Py_TYPE(op)->tp_as_number;
231+
if (nb == NULL || nb->nb_float == NULL) {
232+
PyErr_Format(PyExc_TypeError, "must be real number, not %.50s",
233+
op->ob_type->tp_name);
241234
return -1;
242235
}
243236

244-
val = PyFloat_AS_DOUBLE(fo);
245-
Py_DECREF(fo);
237+
res = (*nb->nb_float) (op);
238+
if (res == NULL) {
239+
return -1;
240+
}
241+
if (!PyFloat_CheckExact(res)) {
242+
if (!PyFloat_Check(res)) {
243+
PyErr_Format(PyExc_TypeError,
244+
"%.50s.__float__ returned non-float (type %.50s)",
245+
op->ob_type->tp_name, res->ob_type->tp_name);
246+
Py_DECREF(res);
247+
return -1;
248+
}
249+
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
250+
"%.50s.__float__ returned non-float (type %.50s). "
251+
"The ability to return an instance of a strict subclass of float "
252+
"is deprecated, and may be removed in a future version of Python.",
253+
op->ob_type->tp_name, res->ob_type->tp_name)) {
254+
Py_DECREF(res);
255+
return -1;
256+
}
257+
}
246258

259+
val = PyFloat_AS_DOUBLE(res);
260+
Py_DECREF(res);
247261
return val;
248262
}
249263

0 commit comments

Comments
 (0)