From e2ec0b27c02a158d0007c11dcc1f2d7a95948712 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 9 Oct 2020 14:14:37 +0300 Subject: [PATCH] bpo-41974: Remove complex.__float__, complex.__floordiv__, etc (GH-22593) Remove complex special methods __int__, __float__, __floordiv__, __mod__, __divmod__, __rfloordiv__, __rmod__ and __rdivmod__ which always raised a TypeError. --- Doc/whatsnew/3.10.rst | 6 ++ Lib/test/test_complex.py | 62 ++++++++++++++++--- .../2020-10-08-09-58-19.bpo-41974.8B-q8O.rst | 4 ++ Objects/abstract.c | 10 +-- Objects/bytesobject.c | 2 +- Objects/complexobject.c | 59 ++++-------------- Objects/floatobject.c | 2 +- Objects/unicodeobject.c | 2 +- 8 files changed, 81 insertions(+), 66 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2020-10-08-09-58-19.bpo-41974.8B-q8O.rst diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index 4ada4be3b66..7401ba722fb 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -255,6 +255,12 @@ Deprecated Removed ======= +* Removed special methods ``__int__``, ``__float__``, ``__floordiv__``, + ``__mod__``, ``__divmod__``, ``__rfloordiv__``, ``__rmod__`` and + ``__rdivmod__`` of the :class:`complex` class. They always raised + a :exc:`TypeError`. + (Contributed by Serhiy Storchaka in :issue:`41974`.) + * The ``ParserBase.error()`` method from the private and undocumented ``_markupbase`` module has been removed. :class:`html.parser.HTMLParser` is the only subclass of ``ParserBase`` and its ``error()`` implementation has already been removed in diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index d1f241f7a60..af39ee878dc 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -11,6 +11,14 @@ INF = float("inf") NAN = float("nan") # These tests ensure that complex math does the right thing +ZERO_DIVISION = ( + (1+1j, 0+0j), + (1+1j, 0.0), + (1+1j, 0), + (1.0, 0+0j), + (1, 0+0j), +) + class ComplexTest(unittest.TestCase): def assertAlmostEqual(self, a, b): @@ -99,20 +107,34 @@ class ComplexTest(unittest.TestCase): self.check_div(complex(random(), random()), complex(random(), random())) - self.assertRaises(ZeroDivisionError, complex.__truediv__, 1+1j, 0+0j) - self.assertRaises(OverflowError, pow, 1e200+1j, 1e200+1j) - self.assertAlmostEqual(complex.__truediv__(2+0j, 1+1j), 1-1j) - self.assertRaises(ZeroDivisionError, complex.__truediv__, 1+1j, 0+0j) for denom_real, denom_imag in [(0, NAN), (NAN, 0), (NAN, NAN)]: z = complex(0, 0) / complex(denom_real, denom_imag) self.assertTrue(isnan(z.real)) self.assertTrue(isnan(z.imag)) + def test_truediv_zero_division(self): + for a, b in ZERO_DIVISION: + with self.assertRaises(ZeroDivisionError): + a / b + def test_floordiv(self): - self.assertRaises(TypeError, complex.__floordiv__, 3+0j, 1.5+0j) - self.assertRaises(TypeError, complex.__floordiv__, 3+0j, 0+0j) + with self.assertRaises(TypeError): + (1+1j) // (1+0j) + with self.assertRaises(TypeError): + (1+1j) // 1.0 + with self.assertRaises(TypeError): + (1+1j) // 1 + with self.assertRaises(TypeError): + 1.0 // (1+0j) + with self.assertRaises(TypeError): + 1 // (1+0j) + + def test_floordiv_zero_division(self): + for a, b in ZERO_DIVISION: + with self.assertRaises(TypeError): + a // b def test_richcompare(self): self.assertIs(complex.__eq__(1+1j, 1<<10000), False) @@ -159,13 +181,32 @@ class ComplexTest(unittest.TestCase): def test_mod(self): # % is no longer supported on complex numbers - self.assertRaises(TypeError, (1+1j).__mod__, 0+0j) - self.assertRaises(TypeError, lambda: (3.33+4.43j) % 0) - self.assertRaises(TypeError, (1+1j).__mod__, 4.3j) + with self.assertRaises(TypeError): + (1+1j) % (1+0j) + with self.assertRaises(TypeError): + (1+1j) % 1.0 + with self.assertRaises(TypeError): + (1+1j) % 1 + with self.assertRaises(TypeError): + 1.0 % (1+0j) + with self.assertRaises(TypeError): + 1 % (1+0j) + + def test_mod_zero_division(self): + for a, b in ZERO_DIVISION: + with self.assertRaises(TypeError): + a % b def test_divmod(self): self.assertRaises(TypeError, divmod, 1+1j, 1+0j) - self.assertRaises(TypeError, divmod, 1+1j, 0+0j) + self.assertRaises(TypeError, divmod, 1+1j, 1.0) + self.assertRaises(TypeError, divmod, 1+1j, 1) + self.assertRaises(TypeError, divmod, 1.0, 1+0j) + self.assertRaises(TypeError, divmod, 1, 1+0j) + + def test_divmod_zero_division(self): + for a, b in ZERO_DIVISION: + self.assertRaises(TypeError, divmod, a, b) def test_pow(self): self.assertAlmostEqual(pow(1+1j, 0+0j), 1.0) @@ -174,6 +215,7 @@ class ComplexTest(unittest.TestCase): self.assertAlmostEqual(pow(1j, -1), 1/1j) self.assertAlmostEqual(pow(1j, 200), 1) self.assertRaises(ValueError, pow, 1+1j, 1+1j, 1+1j) + self.assertRaises(OverflowError, pow, 1e200+1j, 1e200+1j) a = 3.33+4.43j self.assertEqual(a ** 0j, 1) diff --git a/Misc/NEWS.d/next/Core and Builtins/2020-10-08-09-58-19.bpo-41974.8B-q8O.rst b/Misc/NEWS.d/next/Core and Builtins/2020-10-08-09-58-19.bpo-41974.8B-q8O.rst new file mode 100644 index 00000000000..034cfede84b --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2020-10-08-09-58-19.bpo-41974.8B-q8O.rst @@ -0,0 +1,4 @@ +Removed special methods ``__int__``, ``__float__``, ``__floordiv__``, +``__mod__``, ``__divmod__``, ``__rfloordiv__``, ``__rmod__`` and +``__rdivmod__`` of the :class:`complex` class. They always raised +a :exc:`TypeError`. diff --git a/Objects/abstract.c b/Objects/abstract.c index c30fb4eb6a6..2ab3371a3f3 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -747,10 +747,10 @@ done: int PyNumber_Check(PyObject *o) { - return o && Py_TYPE(o)->tp_as_number && - (Py_TYPE(o)->tp_as_number->nb_index || - Py_TYPE(o)->tp_as_number->nb_int || - Py_TYPE(o)->tp_as_number->nb_float); + if (o == NULL) + return 0; + PyNumberMethods *nb = Py_TYPE(o)->tp_as_number; + return nb && (nb->nb_index || nb->nb_int || nb->nb_float || PyComplex_Check(o)); } /* Binary operators */ @@ -1461,7 +1461,7 @@ PyNumber_Long(PyObject *o) } return type_error("int() argument must be a string, a bytes-like object " - "or a number, not '%.200s'", o); + "or a real number, not '%.200s'", o); } PyObject * diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c index 836a736037b..990730cd8cd 100644 --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -522,7 +522,7 @@ formatlong(PyObject *v, int flags, int prec, int type) PyErr_Format(PyExc_TypeError, "%%%c format: %s is required, not %.200s", type, (type == 'o' || type == 'x' || type == 'X') ? "an integer" - : "a number", + : "a real number", Py_TYPE(v)->tp_name); return NULL; } diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 69f6c17b4a4..5ab839a9e94 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -509,23 +509,6 @@ complex_div(PyObject *v, PyObject *w) return PyComplex_FromCComplex(quot); } -static PyObject * -complex_remainder(PyObject *v, PyObject *w) -{ - PyErr_SetString(PyExc_TypeError, - "can't mod complex numbers."); - return NULL; -} - - -static PyObject * -complex_divmod(PyObject *v, PyObject *w) -{ - PyErr_SetString(PyExc_TypeError, - "can't take floor or mod of complex number."); - return NULL; -} - static PyObject * complex_pow(PyObject *v, PyObject *w, PyObject *z) { @@ -562,14 +545,6 @@ complex_pow(PyObject *v, PyObject *w, PyObject *z) return PyComplex_FromCComplex(p); } -static PyObject * -complex_int_div(PyObject *v, PyObject *w) -{ - PyErr_SetString(PyExc_TypeError, - "can't take floor of complex number."); - return NULL; -} - static PyObject * complex_neg(PyComplexObject *v) { @@ -668,22 +643,6 @@ Unimplemented: Py_RETURN_NOTIMPLEMENTED; } -static PyObject * -complex_int(PyObject *v) -{ - PyErr_SetString(PyExc_TypeError, - "can't convert complex to int"); - return NULL; -} - -static PyObject * -complex_float(PyObject *v) -{ - PyErr_SetString(PyExc_TypeError, - "can't convert complex to float"); - return NULL; -} - /*[clinic input] complex.conjugate @@ -966,7 +925,9 @@ complex_new_impl(PyTypeObject *type, PyObject *r, PyObject *i) } nbr = Py_TYPE(r)->tp_as_number; - if (nbr == NULL || (nbr->nb_float == NULL && nbr->nb_index == NULL)) { + if (nbr == NULL || + (nbr->nb_float == NULL && nbr->nb_index == NULL && !PyComplex_Check(r))) + { PyErr_Format(PyExc_TypeError, "complex() first argument must be a string or a number, " "not '%.200s'", @@ -978,7 +939,9 @@ complex_new_impl(PyTypeObject *type, PyObject *r, PyObject *i) } if (i != NULL) { nbi = Py_TYPE(i)->tp_as_number; - if (nbi == NULL || (nbi->nb_float == NULL && nbi->nb_index == NULL)) { + if (nbi == NULL || + (nbi->nb_float == NULL && nbi->nb_index == NULL && !PyComplex_Check(i))) + { PyErr_Format(PyExc_TypeError, "complex() second argument must be a number, " "not '%.200s'", @@ -1057,8 +1020,8 @@ static PyNumberMethods complex_as_number = { (binaryfunc)complex_add, /* nb_add */ (binaryfunc)complex_sub, /* nb_subtract */ (binaryfunc)complex_mul, /* nb_multiply */ - (binaryfunc)complex_remainder, /* nb_remainder */ - (binaryfunc)complex_divmod, /* nb_divmod */ + 0, /* nb_remainder */ + 0, /* nb_divmod */ (ternaryfunc)complex_pow, /* nb_power */ (unaryfunc)complex_neg, /* nb_negative */ (unaryfunc)complex_pos, /* nb_positive */ @@ -1070,9 +1033,9 @@ static PyNumberMethods complex_as_number = { 0, /* nb_and */ 0, /* nb_xor */ 0, /* nb_or */ - complex_int, /* nb_int */ + 0, /* nb_int */ 0, /* nb_reserved */ - complex_float, /* nb_float */ + 0, /* nb_float */ 0, /* nb_inplace_add */ 0, /* nb_inplace_subtract */ 0, /* nb_inplace_multiply*/ @@ -1083,7 +1046,7 @@ static PyNumberMethods complex_as_number = { 0, /* nb_inplace_and */ 0, /* nb_inplace_xor */ 0, /* nb_inplace_or */ - (binaryfunc)complex_int_div, /* nb_floor_divide */ + 0, /* nb_floor_divide */ (binaryfunc)complex_div, /* nb_true_divide */ 0, /* nb_inplace_floor_divide */ 0, /* nb_inplace_true_divide */ diff --git a/Objects/floatobject.c b/Objects/floatobject.c index d0af0ea1a98..828bde18df7 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -215,7 +215,7 @@ PyFloat_FromString(PyObject *v) } else { PyErr_Format(PyExc_TypeError, - "float() argument must be a string or a number, not '%.200s'", + "float() argument must be a string or a real number, not '%.200s'", Py_TYPE(v)->tp_name); return NULL; } diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 6ae06a508c6..01e5c728b38 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -14839,7 +14839,7 @@ wrongtype: break; default: PyErr_Format(PyExc_TypeError, - "%%%c format: a number is required, " + "%%%c format: a real number is required, " "not %.200s", type, Py_TYPE(v)->tp_name); break;