From 9035969533a4c7def622e142dd8606d0a1291538 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 27 May 2024 19:23:10 +0300 Subject: [PATCH 1/8] gh-109218: Deprecate weird cases in the complex() constructor * Passing a string as the "real" keyword argument is now an error; it should only be passed as a single positional argument. * Passing a complex number as the *real* or *imag* argument is now deprecated; it should only be passed as a single positional argument. --- Doc/library/functions.rst | 4 + Doc/whatsnew/3.14.rst | 4 + Lib/test/test_complex.py | 109 ++++++++++------ Lib/test/test_fractions.py | 5 +- ...-05-27-19-13-49.gh-issue-109218.-sdDg0.rst | 3 + Objects/complexobject.c | 119 ++++++++++++++---- 6 files changed, 185 insertions(+), 59 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-05-27-19-13-49.gh-issue-109218.-sdDg0.rst diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst index cb9b650badcfbd..0959f5933b40ff 100644 --- a/Doc/library/functions.rst +++ b/Doc/library/functions.rst @@ -404,6 +404,10 @@ are always available. They are listed here in alphabetical order. Falls back to :meth:`~object.__index__` if :meth:`~object.__complex__` and :meth:`~object.__float__` are not defined. + .. deprecated:: 3.14 + Passing a complex number as the *real* or *imag* argument is now + deprecated; it should only be passed as a single positional argument. + .. function:: delattr(object, name) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index bc12d4b3b590dd..bb469cc3053ae5 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -103,6 +103,10 @@ Optimizations Deprecated ========== +* Passing a complex number as the *real* or *imag* argument in the + :func:`complex` constructor is now deprecated; it should only be passed + as a single positional argument. + (Contributed by Serhiy Storchaka in :gh:`109218`.) Removed diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index fa3017b24e16c8..9638c000ecea86 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -21,6 +21,15 @@ (1, 0+0j), ) +class ComplexSubclass(complex): + pass + +class MockComplex: + def __init__(self, value): + self.value = value + def __complex__(self): + return self.value + class ComplexTest(unittest.TestCase): def assertAlmostEqual(self, a, b): @@ -340,16 +349,13 @@ def test_conjugate(self): self.assertClose(complex(5.3, 9.8).conjugate(), 5.3-9.8j) def test_constructor(self): - class NS: - def __init__(self, value): self.value = value - def __complex__(self): return self.value - self.assertEqual(complex(NS(1+10j)), 1+10j) - self.assertRaises(TypeError, complex, NS(None)) + self.assertEqual(complex(MockComplex(1+10j)), 1+10j) + self.assertRaises(TypeError, complex, MockComplex(None)) self.assertRaises(TypeError, complex, {}) - self.assertRaises(TypeError, complex, NS(1.5)) - self.assertRaises(TypeError, complex, NS(1)) + self.assertRaises(TypeError, complex, MockComplex(1.5)) + self.assertRaises(TypeError, complex, MockComplex(1)) self.assertRaises(TypeError, complex, object()) - self.assertRaises(TypeError, complex, NS(4.25+0.5j), object()) + self.assertRaises(TypeError, complex, MockComplex(4.25+0.5j), object()) self.assertAlmostEqual(complex("1+10j"), 1+10j) self.assertAlmostEqual(complex(10), 10+0j) @@ -369,13 +375,33 @@ def __complex__(self): return self.value self.assertAlmostEqual(complex(3.14), 3.14+0j) self.assertAlmostEqual(complex(314), 314.0+0j) self.assertAlmostEqual(complex(314), 314.0+0j) - self.assertAlmostEqual(complex(3.14+0j, 0j), 3.14+0j) + with self.assertWarnsRegex(DeprecationWarning, + "argument 'imag' must be a real number, not complex"): + self.assertAlmostEqual(complex(3.14+0j, 0j), 3.14+0j) self.assertAlmostEqual(complex(3.14, 0.0), 3.14+0j) self.assertAlmostEqual(complex(314, 0), 314.0+0j) self.assertAlmostEqual(complex(314, 0), 314.0+0j) - self.assertAlmostEqual(complex(0j, 3.14j), -3.14+0j) - self.assertAlmostEqual(complex(0.0, 3.14j), -3.14+0j) - self.assertAlmostEqual(complex(0j, 3.14), 3.14j) + with self.assertWarnsRegex(DeprecationWarning, + "argument 'real' must be a real number, not complex"): + self.assertAlmostEqual(complex(0j, 3.14j), -3.14+0j) + with self.assertWarnsRegex(DeprecationWarning, + "argument 'imag' must be a real number, not complex"): + self.assertAlmostEqual(complex(0.0, 3.14j), -3.14+0j) + with self.assertWarnsRegex(DeprecationWarning, + "argument 'real' must be a real number, not complex"): + self.assertAlmostEqual(complex(0j, 3.14), 3.14j) + with self.assertWarnsRegex(DeprecationWarning, + "argument 'real' must be a real number, not complex"): + self.assertAlmostEqual(complex(3.14+0j, 0), 3.14+0j) + with self.assertWarnsRegex(DeprecationWarning, + "argument 'real' must be a real number, not .*MockComplex"): + self.assertAlmostEqual(complex(MockComplex(3.14+0j), 0), 3.14+0j) + with self.assertWarnsRegex(DeprecationWarning, + "argument 'imag' must be a real number, not .*complex"): + self.assertAlmostEqual(complex(0, 3.14+0j), 3.14j) + with self.assertRaisesRegex(TypeError, + "argument 'imag' must be a real number, not .*MockComplex"): + complex(0, MockComplex(3.14+0j)) self.assertAlmostEqual(complex(0.0, 3.14), 3.14j) self.assertAlmostEqual(complex("1"), 1+0j) self.assertAlmostEqual(complex("1j"), 1j) @@ -398,12 +424,32 @@ def __complex__(self): return self.value self.assertEqual(complex('1-1j'), 1.0 - 1j) self.assertEqual(complex('1J'), 1j) - class complex2(complex): pass - self.assertAlmostEqual(complex(complex2(1+1j)), 1+1j) + self.assertAlmostEqual(complex(ComplexSubclass(1+1j)), 1+1j) self.assertAlmostEqual(complex(real=17, imag=23), 17+23j) - self.assertAlmostEqual(complex(real=17+23j), 17+23j) - self.assertAlmostEqual(complex(real=17+23j, imag=23), 17+46j) - self.assertAlmostEqual(complex(real=1+2j, imag=3+4j), -3+5j) + with self.assertWarnsRegex(DeprecationWarning, + "argument 'real' must be a real number, not complex"): + self.assertAlmostEqual(complex(real=17+23j), 17+23j) + with self.assertWarnsRegex(DeprecationWarning, + "argument 'real' must be a real number, not complex"): + self.assertAlmostEqual(complex(real=17+23j, imag=23), 17+46j) + with self.assertWarnsRegex(DeprecationWarning, + "argument 'real' must be a real number, not complex"): + self.assertAlmostEqual(complex(real=1+2j, imag=3+4j), -3+5j) + with self.assertWarnsRegex(DeprecationWarning, + "argument 'real' must be a real number, not complex"): + self.assertAlmostEqual(complex(real=3.14+0j), 3.14+0j) + with self.assertWarnsRegex(DeprecationWarning, + "argument 'real' must be a real number, not .*MockComplex"): + self.assertAlmostEqual(complex(real=MockComplex(3.14+0j)), 3.14+0j) + with self.assertRaisesRegex(TypeError, + "argument 'real' must be a real number, not str"): + complex(real='1') + with self.assertRaisesRegex(TypeError, + "argument 'real' must be a real number, not str"): + complex('1', 0) + with self.assertRaisesRegex(TypeError, + "argument 'imag' must be a real number, not str"): + complex(0, '1') # check that the sign of a zero in the real or imaginary part # is preserved when constructing from two floats. (These checks @@ -432,8 +478,9 @@ def split_zeros(x): self.assertRaises(TypeError, int, 5+3j) self.assertRaises(TypeError, float, 5+3j) self.assertRaises(ValueError, complex, "") - self.assertRaises(TypeError, complex, None) - self.assertRaisesRegex(TypeError, "not 'NoneType'", complex, None) + self.assertRaisesRegex(TypeError, + "argument must be a string or a number, not NoneType", + complex, None) self.assertRaises(ValueError, complex, "\0") self.assertRaises(ValueError, complex, "3\09") self.assertRaises(TypeError, complex, "1", "2") @@ -453,11 +500,11 @@ def split_zeros(x): self.assertRaises(ValueError, complex, ")1+2j(") self.assertRaisesRegex( TypeError, - "first argument must be a string or a number, not 'dict'", + "argument 'real' must be a real number, not dict", complex, {1:2}, 1) self.assertRaisesRegex( TypeError, - "second argument must be a number, not 'dict'", + "argument 'imag' must be a real number, not dict", complex, 1, {1:2}) # the following three are accepted by Python 2.6 self.assertRaises(ValueError, complex, "1..1j") @@ -537,33 +584,28 @@ def test___complex__(self): self.assertEqual(z.__complex__(), z) self.assertEqual(type(z.__complex__()), complex) - class complex_subclass(complex): - pass - - z = complex_subclass(3 + 4j) + z = ComplexSubclass(3 + 4j) self.assertEqual(z.__complex__(), 3 + 4j) self.assertEqual(type(z.__complex__()), complex) @support.requires_IEEE_754 def test_constructor_special_numbers(self): - class complex2(complex): - pass for x in 0.0, -0.0, INF, -INF, NAN: for y in 0.0, -0.0, INF, -INF, NAN: with self.subTest(x=x, y=y): z = complex(x, y) self.assertFloatsAreIdentical(z.real, x) self.assertFloatsAreIdentical(z.imag, y) - z = complex2(x, y) - self.assertIs(type(z), complex2) + z = ComplexSubclass(x, y) + self.assertIs(type(z), ComplexSubclass) self.assertFloatsAreIdentical(z.real, x) self.assertFloatsAreIdentical(z.imag, y) - z = complex(complex2(x, y)) + z = complex(ComplexSubclass(x, y)) self.assertIs(type(z), complex) self.assertFloatsAreIdentical(z.real, x) self.assertFloatsAreIdentical(z.imag, y) - z = complex2(complex(x, y)) - self.assertIs(type(z), complex2) + z = ComplexSubclass(complex(x, y)) + self.assertIs(type(z), ComplexSubclass) self.assertFloatsAreIdentical(z.real, x) self.assertFloatsAreIdentical(z.imag, y) @@ -645,9 +687,6 @@ def test(v, expected, test_fn=self.assertEqual): test(complex(-0., -0.), "(-0-0j)") def test_pos(self): - class ComplexSubclass(complex): - pass - self.assertEqual(+(1+6j), 1+6j) self.assertEqual(+ComplexSubclass(1, 6), 1+6j) self.assertIs(type(+ComplexSubclass(1, 6)), complex) diff --git a/Lib/test/test_fractions.py b/Lib/test/test_fractions.py index 3a714c64278847..b90bef97e537fa 100644 --- a/Lib/test/test_fractions.py +++ b/Lib/test/test_fractions.py @@ -806,7 +806,10 @@ def testMixedMultiplication(self): self.assertTypedEquals(F(3, 2) * Polar(4, 2), Polar(F(6, 1), 2)) self.assertTypedEquals(F(3, 2) * Polar(4.0, 2), Polar(6.0, 2)) self.assertTypedEquals(F(3, 2) * Rect(4, 3), Rect(F(6, 1), F(9, 2))) - self.assertTypedEquals(F(3, 2) * RectComplex(4, 3), RectComplex(6.0+0j, 4.5+0j)) + with self.assertWarnsRegex(DeprecationWarning, + "argument 'real' must be a real number, not complex"): + self.assertTypedEquals(F(3, 2) * RectComplex(4, 3), + RectComplex(6.0+0j, 4.5+0j)) self.assertRaises(TypeError, operator.mul, Polar(4, 2), F(3, 2)) self.assertTypedEquals(Rect(4, 3) * F(3, 2), 6.0 + 4.5j) self.assertEqual(F(3, 2) * SymbolicComplex('X'), SymbolicComplex('3/2 * X')) diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-05-27-19-13-49.gh-issue-109218.-sdDg0.rst b/Misc/NEWS.d/next/Core and Builtins/2024-05-27-19-13-49.gh-issue-109218.-sdDg0.rst new file mode 100644 index 00000000000000..db762174a8c1e1 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-05-27-19-13-49.gh-issue-109218.-sdDg0.rst @@ -0,0 +1,3 @@ +:func:`complex` accepts now a string only as a positional argument. Passing +a complex number as the "real" or "imag" argument is deprecated; it should +only be passed as a single positional argument. diff --git a/Objects/complexobject.c b/Objects/complexobject.c index d8b0e84da5df4a..001d4a9501e7f2 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -894,8 +894,8 @@ complex_subtype_from_string(PyTypeObject *type, PyObject *v) } else { PyErr_Format(PyExc_TypeError, - "complex() argument must be a string or a number, not '%.200s'", - Py_TYPE(v)->tp_name); + "complex() argument must be a string or a number, not %T", + v); return NULL; } @@ -905,6 +905,74 @@ complex_subtype_from_string(PyTypeObject *type, PyObject *v) return result; } +static PyObject * +actual_complex_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + PyObject *tmp; + PyObject *res = NULL; + PyNumberMethods *nbr; + int own_arg = 0; + + if (PyTuple_GET_SIZE(args) > 1 || (kwargs != NULL && PyDict_GET_SIZE(kwargs))) { + return complex_new(type, args, kwargs); + } + if (!PyTuple_GET_SIZE(args)) { + return complex_subtype_from_doubles(type, 0, 0); + } + + PyObject *arg = PyTuple_GET_ITEM(args, 0); + /* Special-case for a single argument when type(arg) is complex. */ + if (PyComplex_CheckExact(arg) && type == &PyComplex_Type) { + /* Note that we can't know whether it's safe to return + a complex *subclass* instance as-is, hence the restriction + to exact complexes here. If either the input or the + output is a complex subclass, it will be handled below + as a non-orthogonal vector. */ + return Py_NewRef(arg); + } + if (PyUnicode_Check(arg)) { + return complex_subtype_from_string(type, arg); + } + tmp = try_complex_special_method(arg); + if (tmp) { + arg = tmp; + own_arg = 1; + } + else if (PyErr_Occurred()) { + return NULL; + } + + if (PyComplex_Check(arg)) { + /* Note that if arg is of a complex subtype, we're only + retaining its real & imag parts here, and the return + value is (properly) of the builtin complex type. */ + Py_complex c = ((PyComplexObject*)arg)->cval; + res = complex_subtype_from_doubles(type, c.real, c.imag); + } + else if ((nbr = Py_TYPE(arg)->tp_as_number) != NULL && + (nbr->nb_float != NULL || nbr->nb_index != NULL)) + { + /* The argument really is entirely real, and contributes + nothing in the imaginary direction. + Just treat it as a double. */ + double r = PyFloat_AsDouble(arg); + if (r != -1.0 || !PyErr_Occurred()) { + res = complex_subtype_from_doubles(type, r, 0); + } + } + else { + PyErr_Format(PyExc_TypeError, + "complex() argument must be a string or a number, not %T", + arg); + } + if (own_arg) { + /* arg was a newly created complex number, rather + than the original "real" argument. */ + Py_DECREF(arg); + } + return res; +} + /*[clinic input] @classmethod complex.__new__ as complex_new @@ -930,10 +998,16 @@ complex_new_impl(PyTypeObject *type, PyObject *r, PyObject *i) if (r == NULL) { r = _PyLong_GetZero(); } + PyObject *orig_r = r; /* Special-case for a single argument when type(arg) is complex. */ if (PyComplex_CheckExact(r) && i == NULL && type == &PyComplex_Type) { + if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1, + "complex() argument 'real' must be a real number, not %T", + r)) { + return NULL; + } /* Note that we can't know whether it's safe to return a complex *subclass* instance as-is, hence the restriction to exact complexes here. If either the input or the @@ -941,20 +1015,6 @@ complex_new_impl(PyTypeObject *type, PyObject *r, PyObject *i) as a non-orthogonal vector. */ return Py_NewRef(r); } - if (PyUnicode_Check(r)) { - if (i != NULL) { - PyErr_SetString(PyExc_TypeError, - "complex() can't take second arg" - " if first is a string"); - return NULL; - } - return complex_subtype_from_string(type, r); - } - if (i != NULL && PyUnicode_Check(i)) { - PyErr_SetString(PyExc_TypeError, - "complex() second arg can't be a string"); - return NULL; - } tmp = try_complex_special_method(r); if (tmp) { @@ -970,9 +1030,8 @@ complex_new_impl(PyTypeObject *type, PyObject *r, PyObject *i) (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'", - Py_TYPE(r)->tp_name); + "complex() argument 'real' must be a real number, not %T", + r); if (own_r) { Py_DECREF(r); } @@ -984,9 +1043,8 @@ complex_new_impl(PyTypeObject *type, PyObject *r, PyObject *i) (nbi->nb_float == NULL && nbi->nb_index == NULL && !PyComplex_Check(i))) { PyErr_Format(PyExc_TypeError, - "complex() second argument must be a number, " - "not '%.200s'", - Py_TYPE(i)->tp_name); + "complex() argument 'imag' must be a real number, not %T", + i); if (own_r) { Py_DECREF(r); } @@ -1010,6 +1068,16 @@ complex_new_impl(PyTypeObject *type, PyObject *r, PyObject *i) if (own_r) { Py_DECREF(r); } + nbr = Py_TYPE(orig_r)->tp_as_number; + if (nbr == NULL || + (nbr->nb_float == NULL && nbr->nb_index == NULL)) + { + if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1, + "complex() argument 'real' must be a real number, not %T", + orig_r)) { + return NULL; + } + } } else { /* The "real" part really is entirely real, and contributes @@ -1032,6 +1100,11 @@ complex_new_impl(PyTypeObject *type, PyObject *r, PyObject *i) ci.real = cr.imag; } else if (PyComplex_Check(i)) { + if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1, + "complex() argument 'imag' must be a real number, not %T", + i)) { + return NULL; + } ci = ((PyComplexObject*)i)->cval; ci_is_complex = 1; } else { @@ -1131,6 +1204,6 @@ PyTypeObject PyComplex_Type = { 0, /* tp_dictoffset */ 0, /* tp_init */ PyType_GenericAlloc, /* tp_alloc */ - complex_new, /* tp_new */ + actual_complex_new, /* tp_new */ PyObject_Del, /* tp_free */ }; From c35779bc2030d6b1d66749a622fe801b3f3dd51e Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 27 May 2024 21:28:28 +0300 Subject: [PATCH 2/8] gh-109218: Refactor tests for the complex() constructor * Share common classes. * Use exactly representable floats and exact tests. * Check the sign of zero components. * Remove duplicated tests (mostly left after merging int and long). * Reorder tests in more consistent way. * Test more error messages. * Add tests for missed cases. --- Lib/test/test_complex.py | 346 +++++++++++++++++++++------------------ 1 file changed, 184 insertions(+), 162 deletions(-) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index fa3017b24e16c8..11b471e911b511 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -5,7 +5,7 @@ INVALID_UNDERSCORE_LITERALS) from random import random -from math import atan2, isnan, copysign +from math import isnan, copysign import operator INF = float("inf") @@ -21,6 +21,27 @@ (1, 0+0j), ) +class MockIndex: + def __init__(self, value): + self.value = value + def __index__(self): + return self.value + +class MockFloat: + def __init__(self, value): + self.value = value + def __float__(self): + return self.value + +class ComplexSubclass(complex): + pass + +class MockComplex: + def __init__(self, value): + self.value = value + def __complex__(self): + return self.value + class ComplexTest(unittest.TestCase): def assertAlmostEqual(self, a, b): @@ -340,137 +361,84 @@ def test_conjugate(self): self.assertClose(complex(5.3, 9.8).conjugate(), 5.3-9.8j) def test_constructor(self): - class NS: - def __init__(self, value): self.value = value - def __complex__(self): return self.value - self.assertEqual(complex(NS(1+10j)), 1+10j) - self.assertRaises(TypeError, complex, NS(None)) - self.assertRaises(TypeError, complex, {}) - self.assertRaises(TypeError, complex, NS(1.5)) - self.assertRaises(TypeError, complex, NS(1)) - self.assertRaises(TypeError, complex, object()) - self.assertRaises(TypeError, complex, NS(4.25+0.5j), object()) - - self.assertAlmostEqual(complex("1+10j"), 1+10j) - self.assertAlmostEqual(complex(10), 10+0j) - self.assertAlmostEqual(complex(10.0), 10+0j) - self.assertAlmostEqual(complex(10), 10+0j) - self.assertAlmostEqual(complex(10+0j), 10+0j) - self.assertAlmostEqual(complex(1,10), 1+10j) - self.assertAlmostEqual(complex(1,10), 1+10j) - self.assertAlmostEqual(complex(1,10.0), 1+10j) - self.assertAlmostEqual(complex(1,10), 1+10j) - self.assertAlmostEqual(complex(1,10), 1+10j) - self.assertAlmostEqual(complex(1,10.0), 1+10j) - self.assertAlmostEqual(complex(1.0,10), 1+10j) - self.assertAlmostEqual(complex(1.0,10), 1+10j) - self.assertAlmostEqual(complex(1.0,10.0), 1+10j) - self.assertAlmostEqual(complex(3.14+0j), 3.14+0j) - self.assertAlmostEqual(complex(3.14), 3.14+0j) - self.assertAlmostEqual(complex(314), 314.0+0j) - self.assertAlmostEqual(complex(314), 314.0+0j) - self.assertAlmostEqual(complex(3.14+0j, 0j), 3.14+0j) - self.assertAlmostEqual(complex(3.14, 0.0), 3.14+0j) - self.assertAlmostEqual(complex(314, 0), 314.0+0j) - self.assertAlmostEqual(complex(314, 0), 314.0+0j) - self.assertAlmostEqual(complex(0j, 3.14j), -3.14+0j) - self.assertAlmostEqual(complex(0.0, 3.14j), -3.14+0j) - self.assertAlmostEqual(complex(0j, 3.14), 3.14j) - self.assertAlmostEqual(complex(0.0, 3.14), 3.14j) - self.assertAlmostEqual(complex("1"), 1+0j) - self.assertAlmostEqual(complex("1j"), 1j) - self.assertAlmostEqual(complex(), 0) - self.assertAlmostEqual(complex("-1"), -1) - self.assertAlmostEqual(complex("+1"), +1) - self.assertAlmostEqual(complex("(1+2j)"), 1+2j) - self.assertAlmostEqual(complex("(1.3+2.2j)"), 1.3+2.2j) - self.assertAlmostEqual(complex("3.14+1J"), 3.14+1j) - self.assertAlmostEqual(complex(" ( +3.14-6J )"), 3.14-6j) - self.assertAlmostEqual(complex(" ( +3.14-J )"), 3.14-1j) - self.assertAlmostEqual(complex(" ( +3.14+j )"), 3.14+1j) - self.assertAlmostEqual(complex("J"), 1j) - self.assertAlmostEqual(complex("( j )"), 1j) - self.assertAlmostEqual(complex("+J"), 1j) - self.assertAlmostEqual(complex("( -j)"), -1j) - self.assertAlmostEqual(complex('1e-500'), 0.0 + 0.0j) - self.assertAlmostEqual(complex('-1e-500j'), 0.0 - 0.0j) - self.assertAlmostEqual(complex('-1e-500+1e-500j'), -0.0 + 0.0j) - self.assertEqual(complex('1-1j'), 1.0 - 1j) - self.assertEqual(complex('1J'), 1j) - - class complex2(complex): pass - self.assertAlmostEqual(complex(complex2(1+1j)), 1+1j) - self.assertAlmostEqual(complex(real=17, imag=23), 17+23j) - self.assertAlmostEqual(complex(real=17+23j), 17+23j) - self.assertAlmostEqual(complex(real=17+23j, imag=23), 17+46j) - self.assertAlmostEqual(complex(real=1+2j, imag=3+4j), -3+5j) + def check(z, x, y): + self.assertIs(type(z), complex) + self.assertFloatsAreIdentical(z.real, x) + self.assertFloatsAreIdentical(z.imag, y) + + check(complex(), 0.0, 0.0) + check(complex(10), 10.0, 0.0) + check(complex(4.25), 4.25, 0.0) + check(complex(4.25+0j), 4.25, 0.0) + check(complex(4.25+0.5j), 4.25, 0.5) + check(complex(ComplexSubclass(4.25+0.5j)), 4.25, 0.5) + check(complex(MockComplex(4.25+0.5j)), 4.25, 0.5) + + check(complex(1, 10), 1.0, 10.0) + check(complex(1, 10.0), 1.0, 10.0) + check(complex(1, 4.25), 1.0, 4.25) + check(complex(1.0, 10), 1.0, 10.0) + check(complex(4.25, 10), 4.25, 10.0) + check(complex(1.0, 10.0), 1.0, 10.0) + check(complex(4.25, 0.5), 4.25, 0.5) + + check(complex(4.25+0j, 0), 4.25, 0.0) + check(complex(ComplexSubclass(4.25+0j), 0), 4.25, 0.0) + check(complex(MockComplex(4.25+0j), 0), 4.25, 0.0) + check(complex(4.25j, 0), 0.0, 4.25) + check(complex(0j, 4.25), 0.0, 4.25) + check(complex(0, 4.25+0j), 0.0, 4.25) + check(complex(0, ComplexSubclass(4.25+0j)), 0.0, 4.25) + with self.assertRaisesRegex(TypeError, + "second argument must be a number, not 'MockComplex'"): + complex(0, MockComplex(4.25+0j)) + check(complex(0.0, 4.25j), -4.25, 0.0) + check(complex(4.25+0j, 0j), 4.25, 0.0) + check(complex(4.25j, 0j), 0.0, 4.25) + check(complex(0j, 4.25+0j), 0.0, 4.25) + check(complex(0j, 4.25j), -4.25, 0.0) + + check(complex(real=4.25), 4.25, 0.0) + check(complex(imag=1.5), 0.0, 1.5) + check(complex(real=4.25, imag=1.5), 4.25, 1.5) + check(complex(4.25, imag=1.5), 4.25, 1.5) # check that the sign of a zero in the real or imaginary part # is preserved when constructing from two floats. (These checks # are harmless on systems without support for signed zeros.) - def split_zeros(x): - """Function that produces different results for 0. and -0.""" - return atan2(x, -1.) - - self.assertEqual(split_zeros(complex(1., 0.).imag), split_zeros(0.)) - self.assertEqual(split_zeros(complex(1., -0.).imag), split_zeros(-0.)) - self.assertEqual(split_zeros(complex(0., 1.).real), split_zeros(0.)) - self.assertEqual(split_zeros(complex(-0., 1.).real), split_zeros(-0.)) - - c = 3.14 + 1j - self.assertTrue(complex(c) is c) + for x in 1.0, -1.0: + for y in 0.0, -0.0: + check(complex(x, y), x, y) + check(complex(y, x), y, x) + + c = complex(4.25, 1.5) + self.assertIs(complex(c), c) + self.assertIsNot(ComplexSubclass(c), c) del c - self.assertRaises(TypeError, complex, "1", "1") - self.assertRaises(TypeError, complex, 1, "1") - - # SF bug 543840: complex(string) accepts strings with \0 - # Fixed in 2.3. - self.assertRaises(ValueError, complex, '1+1j\0j') - - self.assertRaises(TypeError, int, 5+3j) - self.assertRaises(TypeError, int, 5+3j) - self.assertRaises(TypeError, float, 5+3j) - self.assertRaises(ValueError, complex, "") - self.assertRaises(TypeError, complex, None) - self.assertRaisesRegex(TypeError, "not 'NoneType'", complex, None) - self.assertRaises(ValueError, complex, "\0") - self.assertRaises(ValueError, complex, "3\09") - self.assertRaises(TypeError, complex, "1", "2") - self.assertRaises(TypeError, complex, "1", 42) - self.assertRaises(TypeError, complex, 1, "2") - self.assertRaises(ValueError, complex, "1+") - self.assertRaises(ValueError, complex, "1+1j+1j") - self.assertRaises(ValueError, complex, "--") - self.assertRaises(ValueError, complex, "(1+2j") - self.assertRaises(ValueError, complex, "1+2j)") - self.assertRaises(ValueError, complex, "1+(2j)") - self.assertRaises(ValueError, complex, "(1+2j)123") - self.assertRaises(ValueError, complex, "x") - self.assertRaises(ValueError, complex, "1j+2") - self.assertRaises(ValueError, complex, "1e1ej") - self.assertRaises(ValueError, complex, "1e++1ej") - self.assertRaises(ValueError, complex, ")1+2j(") - self.assertRaisesRegex( - TypeError, + self.assertRaisesRegex(TypeError, + "first argument must be a string or a number, not 'dict'", + complex, {}) + self.assertRaisesRegex(TypeError, + "first argument must be a string or a number, not 'NoneType'", + complex, None) + self.assertRaisesRegex(TypeError, "first argument must be a string or a number, not 'dict'", - complex, {1:2}, 1) - self.assertRaisesRegex( - TypeError, + complex, {1:2}, 0) + self.assertRaisesRegex(TypeError, + "can't take second arg if first is a string", + complex, '1', 0) + self.assertRaisesRegex(TypeError, "second argument must be a number, not 'dict'", - complex, 1, {1:2}) - # the following three are accepted by Python 2.6 - self.assertRaises(ValueError, complex, "1..1j") - self.assertRaises(ValueError, complex, "1.11.1j") - self.assertRaises(ValueError, complex, "1e1.1j") + complex, 0, {1:2}) + self.assertRaisesRegex(TypeError, + "second arg can't be a string", + complex, 0, '1') - # check that complex accepts long unicode strings - self.assertEqual(type(complex("1"*500)), complex) - # check whitespace processing - self.assertEqual(complex('\N{EM SPACE}(\N{EN SPACE}1+1j ) '), 1+1j) - # Invalid unicode string - # See bpo-34087 - self.assertRaises(ValueError, complex, '\u3053\u3093\u306b\u3061\u306f') + self.assertRaises(TypeError, complex, MockComplex(1.5)) + self.assertRaises(TypeError, complex, MockComplex(1)) + self.assertRaises(TypeError, complex, MockComplex(None)) + self.assertRaises(TypeError, complex, MockComplex(4.25+0j), object()) class EvilExc(Exception): pass @@ -481,33 +449,33 @@ def __complex__(self): self.assertRaises(EvilExc, complex, evilcomplex()) - class float2: - def __init__(self, value): - self.value = value - def __float__(self): - return self.value - - self.assertAlmostEqual(complex(float2(42.)), 42) - self.assertAlmostEqual(complex(real=float2(17.), imag=float2(23.)), 17+23j) - self.assertRaises(TypeError, complex, float2(None)) - - class MyIndex: - def __init__(self, value): - self.value = value - def __index__(self): - return self.value - - self.assertAlmostEqual(complex(MyIndex(42)), 42.0+0.0j) - self.assertAlmostEqual(complex(123, MyIndex(42)), 123.0+42.0j) - self.assertRaises(OverflowError, complex, MyIndex(2**2000)) - self.assertRaises(OverflowError, complex, 123, MyIndex(2**2000)) + check(complex(MockFloat(4.25)), 4.25, 0.0) + check(complex(MockFloat(4.25), 1.5), 4.25, 1.5) + check(complex(1.5, MockFloat(4.25)), 1.5, 4.25) + self.assertRaises(TypeError, complex, MockFloat(42)) + self.assertRaises(TypeError, complex, MockFloat(42), 1.5) + self.assertRaises(TypeError, complex, 1.5, MockFloat(42)) + self.assertRaises(TypeError, complex, MockFloat(None)) + self.assertRaises(TypeError, complex, MockFloat(None), 1.5) + self.assertRaises(TypeError, complex, 1.5, MockFloat(None)) + + check(complex(MockIndex(42)), 42.0, 0.0) + check(complex(MockIndex(42), 1.5), 42.0, 1.5) + check(complex(1.5, MockIndex(42)), 1.5, 42.0) + self.assertRaises(OverflowError, complex, MockIndex(2**2000)) + self.assertRaises(OverflowError, complex, MockIndex(2**2000), 1.5) + self.assertRaises(OverflowError, complex, 1.5, MockIndex(2**2000)) + self.assertRaises(TypeError, complex, MockIndex(None)) + self.assertRaises(TypeError, complex, MockIndex(None), 1.5) + self.assertRaises(TypeError, complex, 1.5, MockIndex(None)) class MyInt: def __int__(self): return 42 self.assertRaises(TypeError, complex, MyInt()) - self.assertRaises(TypeError, complex, 123, MyInt()) + self.assertRaises(TypeError, complex, MyInt(), 1.5) + self.assertRaises(TypeError, complex, 1.5, MyInt()) class complex0(complex): """Test usage of __complex__() when inheriting from 'complex'""" @@ -527,9 +495,9 @@ class complex2(complex): def __complex__(self): return None - self.assertEqual(complex(complex0(1j)), 42j) + check(complex(complex0(1j)), 0.0, 42.0) with self.assertWarns(DeprecationWarning): - self.assertEqual(complex(complex1(1j)), 2j) + check(complex(complex1(1j)), 0.0, 2.0) self.assertRaises(TypeError, complex, complex2(1j)) def test___complex__(self): @@ -537,36 +505,93 @@ def test___complex__(self): self.assertEqual(z.__complex__(), z) self.assertEqual(type(z.__complex__()), complex) - class complex_subclass(complex): - pass - - z = complex_subclass(3 + 4j) + z = ComplexSubclass(3 + 4j) self.assertEqual(z.__complex__(), 3 + 4j) self.assertEqual(type(z.__complex__()), complex) @support.requires_IEEE_754 def test_constructor_special_numbers(self): - class complex2(complex): - pass for x in 0.0, -0.0, INF, -INF, NAN: for y in 0.0, -0.0, INF, -INF, NAN: with self.subTest(x=x, y=y): z = complex(x, y) self.assertFloatsAreIdentical(z.real, x) self.assertFloatsAreIdentical(z.imag, y) - z = complex2(x, y) - self.assertIs(type(z), complex2) + z = ComplexSubclass(x, y) + self.assertIs(type(z), ComplexSubclass) self.assertFloatsAreIdentical(z.real, x) self.assertFloatsAreIdentical(z.imag, y) - z = complex(complex2(x, y)) + z = complex(ComplexSubclass(x, y)) self.assertIs(type(z), complex) self.assertFloatsAreIdentical(z.real, x) self.assertFloatsAreIdentical(z.imag, y) - z = complex2(complex(x, y)) - self.assertIs(type(z), complex2) + z = ComplexSubclass(complex(x, y)) + self.assertIs(type(z), ComplexSubclass) self.assertFloatsAreIdentical(z.real, x) self.assertFloatsAreIdentical(z.imag, y) + def test_constructor_from_string(self): + def check(z, x, y): + self.assertIs(type(z), complex) + self.assertFloatsAreIdentical(z.real, x) + self.assertFloatsAreIdentical(z.imag, y) + + check(complex("1"), 1.0, 0.0) + check(complex("1j"), 0.0, 1.0) + check(complex("-1"), -1.0, 0.0) + check(complex("+1"), 1.0, 0.0) + check(complex("1+2j"), 1.0, 2.0) + check(complex("(1+2j)"), 1.0, 2.0) + check(complex("(1.5+4.25j)"), 1.5, 4.25) + check(complex("4.25+1J"), 4.25, 1.0) + check(complex(" ( +4.25-6J )"), 4.25, -6.0) + check(complex(" ( +4.25-J )"), 4.25, -1.0) + check(complex(" ( +4.25+j )"), 4.25, 1.0) + check(complex("J"), 0.0, 1.0) + check(complex("( j )"), 0.0, 1.0) + check(complex("+J"), 0.0, 1.0) + check(complex("( -j)"), 0.0, -1.0) + check(complex('1-1j'), 1.0, -1.0) + check(complex('1J'), 0.0, 1.0) + + check(complex('1e-500'), 0.0, 0.0) + check(complex('-1e-500j'), 0.0, -0.0) + check(complex('1e-500+1e-500j'), 0.0, 0.0) + check(complex('-1e-500+1e-500j'), -0.0, 0.0) + check(complex('1e-500-1e-500j'), 0.0, -0.0) + check(complex('-1e-500-1e-500j'), -0.0, -0.0) + + # SF bug 543840: complex(string) accepts strings with \0 + # Fixed in 2.3. + self.assertRaises(ValueError, complex, '1+1j\0j') + self.assertRaises(ValueError, complex, "") + self.assertRaises(ValueError, complex, "\0") + self.assertRaises(ValueError, complex, "3\09") + self.assertRaises(ValueError, complex, "1+") + self.assertRaises(ValueError, complex, "1+1j+1j") + self.assertRaises(ValueError, complex, "--") + self.assertRaises(ValueError, complex, "(1+2j") + self.assertRaises(ValueError, complex, "1+2j)") + self.assertRaises(ValueError, complex, "1+(2j)") + self.assertRaises(ValueError, complex, "(1+2j)123") + self.assertRaises(ValueError, complex, "x") + self.assertRaises(ValueError, complex, "1j+2") + self.assertRaises(ValueError, complex, "1e1ej") + self.assertRaises(ValueError, complex, "1e++1ej") + self.assertRaises(ValueError, complex, ")1+2j(") + # the following three are accepted by Python 2.6 + self.assertRaises(ValueError, complex, "1..1j") + self.assertRaises(ValueError, complex, "1.11.1j") + self.assertRaises(ValueError, complex, "1e1.1j") + + # check that complex accepts long unicode strings + self.assertIs(type(complex("1"*500)), complex) + # check whitespace processing + self.assertEqual(complex('\N{EM SPACE}(\N{EN SPACE}1+1j ) '), 1+1j) + # Invalid unicode string + # See bpo-34087 + self.assertRaises(ValueError, complex, '\u3053\u3093\u306b\u3061\u306f') + def test_constructor_negative_nans_from_string(self): self.assertEqual(copysign(1., complex("-nan").real), -1.) self.assertEqual(copysign(1., complex("-nanj").imag), -1.) @@ -645,9 +670,6 @@ def test(v, expected, test_fn=self.assertEqual): test(complex(-0., -0.), "(-0-0j)") def test_pos(self): - class ComplexSubclass(complex): - pass - self.assertEqual(+(1+6j), 1+6j) self.assertEqual(+ComplexSubclass(1, 6), 1+6j) self.assertIs(type(+ComplexSubclass(1, 6)), complex) @@ -667,8 +689,8 @@ def test_getnewargs(self): def test_plus_minus_0j(self): # test that -0j and 0j literals are not identified z1, z2 = 0j, -0j - self.assertEqual(atan2(z1.imag, -1.), atan2(0., -1.)) - self.assertEqual(atan2(z2.imag, -1.), atan2(-0., -1.)) + self.assertFloatsAreIdentical(z1.imag, 0.0) + self.assertFloatsAreIdentical(z2.imag, -0.0) @support.requires_IEEE_754 def test_negated_imaginary_literal(self): From 537cc4e6a4869bde14b440365f5ffa6129232440 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 28 May 2024 11:19:15 +0300 Subject: [PATCH 3/8] Address review comments. --- Lib/test/test_complex.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index 74a1c00053bcf3..d521253956ac40 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -423,6 +423,12 @@ def check(z, x, y): check(complex(0j, 4.25j), -4.25, 0.0) check(complex(real=4.25), 4.25, 0.0) + with self.assertWarnsRegex(DeprecationWarning, + "argument 'real' must be a real number, not complex"): + check(complex(real=4.25+0j), 4.25, 0.0) + with self.assertWarnsRegex(DeprecationWarning, + "argument 'real' must be a real number, not complex"): + check(complex(real=4.25+1.5j), 4.25, 1.5) check(complex(imag=1.5), 0.0, 1.5) check(complex(real=4.25, imag=1.5), 4.25, 1.5) check(complex(4.25, imag=1.5), 4.25, 1.5) @@ -437,8 +443,15 @@ def check(z, x, y): c = complex(4.25, 1.5) self.assertIs(complex(c), c) - self.assertIsNot(ComplexSubclass(c), c) - del c + c2 = ComplexSubclass(c) + self.assertEqual(c2, c) + self.assertIs(type(c2), ComplexSubclass) + with self.assertWarnsRegex(DeprecationWarning, + "argument 'real' must be a real number, not complex"): + c2 = ComplexSubclass(real=c) + self.assertEqual(c2, c) + self.assertIs(type(c2), ComplexSubclass) + del c, c2 self.assertRaisesRegex(TypeError, "argument must be a string or a number, not dict", @@ -463,6 +476,9 @@ def check(z, x, y): self.assertRaises(TypeError, complex, MockComplex(1)) self.assertRaises(TypeError, complex, MockComplex(None)) self.assertRaises(TypeError, complex, MockComplex(4.25+0j), object()) + self.assertRaises(TypeError, complex, MockComplex(1.5), object()) + self.assertRaises(TypeError, complex, MockComplex(1), object()) + self.assertRaises(TypeError, complex, MockComplex(None), object()) class EvilExc(Exception): pass From e8b6ba9a94fa521b395cfcf9ad476e366de24a22 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 28 May 2024 11:21:30 +0300 Subject: [PATCH 4/8] Add more tests. --- Lib/test/test_complex.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index 11b471e911b511..4d0cb4f3a187ea 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -399,6 +399,8 @@ def check(z, x, y): check(complex(0j, 4.25j), -4.25, 0.0) check(complex(real=4.25), 4.25, 0.0) + check(complex(real=4.25+0j), 4.25, 0.0) + check(complex(real=4.25+1.5j), 4.25, 1.5) check(complex(imag=1.5), 0.0, 1.5) check(complex(real=4.25, imag=1.5), 4.25, 1.5) check(complex(4.25, imag=1.5), 4.25, 1.5) @@ -413,8 +415,13 @@ def check(z, x, y): c = complex(4.25, 1.5) self.assertIs(complex(c), c) - self.assertIsNot(ComplexSubclass(c), c) - del c + c2 = ComplexSubclass(c) + self.assertEqual(c2, c) + self.assertIs(type(c2), ComplexSubclass) + c2 = ComplexSubclass(real=c) + self.assertEqual(c2, c) + self.assertIs(type(c2), ComplexSubclass) + del c, c2 self.assertRaisesRegex(TypeError, "first argument must be a string or a number, not 'dict'", @@ -439,6 +446,9 @@ def check(z, x, y): self.assertRaises(TypeError, complex, MockComplex(1)) self.assertRaises(TypeError, complex, MockComplex(None)) self.assertRaises(TypeError, complex, MockComplex(4.25+0j), object()) + self.assertRaises(TypeError, complex, MockComplex(1.5), object()) + self.assertRaises(TypeError, complex, MockComplex(1), object()) + self.assertRaises(TypeError, complex, MockComplex(None), object()) class EvilExc(Exception): pass From 9e66399e6253b37bbda0ff8b4c3525cb183bc17e Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 28 May 2024 16:54:48 +0300 Subject: [PATCH 5/8] Simplify some code. --- Lib/test/test_complex.py | 5 ----- Objects/complexobject.c | 33 +++++---------------------------- 2 files changed, 5 insertions(+), 33 deletions(-) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index d521253956ac40..0d7e45d6bd1ce2 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -446,11 +446,6 @@ def check(z, x, y): c2 = ComplexSubclass(c) self.assertEqual(c2, c) self.assertIs(type(c2), ComplexSubclass) - with self.assertWarnsRegex(DeprecationWarning, - "argument 'real' must be a real number, not complex"): - c2 = ComplexSubclass(real=c) - self.assertEqual(c2, c) - self.assertIs(type(c2), ComplexSubclass) del c, c2 self.assertRaisesRegex(TypeError, diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 001d4a9501e7f2..cdf173527ce554 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -908,10 +908,8 @@ complex_subtype_from_string(PyTypeObject *type, PyObject *v) static PyObject * actual_complex_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) { - PyObject *tmp; PyObject *res = NULL; PyNumberMethods *nbr; - int own_arg = 0; if (PyTuple_GET_SIZE(args) > 1 || (kwargs != NULL && PyDict_GET_SIZE(kwargs))) { return complex_new(type, args, kwargs); @@ -933,16 +931,16 @@ actual_complex_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) if (PyUnicode_Check(arg)) { return complex_subtype_from_string(type, arg); } - tmp = try_complex_special_method(arg); + PyObject *tmp = try_complex_special_method(arg); if (tmp) { - arg = tmp; - own_arg = 1; + Py_complex c = ((PyComplexObject*)tmp)->cval; + res = complex_subtype_from_doubles(type, c.real, c.imag); + Py_DECREF(tmp); } else if (PyErr_Occurred()) { return NULL; } - - if (PyComplex_Check(arg)) { + else if (PyComplex_Check(arg)) { /* Note that if arg is of a complex subtype, we're only retaining its real & imag parts here, and the return value is (properly) of the builtin complex type. */ @@ -965,11 +963,6 @@ actual_complex_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) "complex() argument must be a string or a number, not %T", arg); } - if (own_arg) { - /* arg was a newly created complex number, rather - than the original "real" argument. */ - Py_DECREF(arg); - } return res; } @@ -1000,22 +993,6 @@ complex_new_impl(PyTypeObject *type, PyObject *r, PyObject *i) } PyObject *orig_r = r; - /* Special-case for a single argument when type(arg) is complex. */ - if (PyComplex_CheckExact(r) && i == NULL && - type == &PyComplex_Type) { - if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1, - "complex() argument 'real' must be a real number, not %T", - r)) { - return NULL; - } - /* Note that we can't know whether it's safe to return - a complex *subclass* instance as-is, hence the restriction - to exact complexes here. If either the input or the - output is a complex subclass, it will be handled below - as a non-orthogonal vector. */ - return Py_NewRef(r); - } - tmp = try_complex_special_method(r); if (tmp) { r = tmp; From 573b2cddf98fbb3928c0369c992002e3f38a08da Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 29 May 2024 00:39:39 +0300 Subject: [PATCH 6/8] Add comments. --- Objects/complexobject.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Objects/complexobject.c b/Objects/complexobject.c index cdf173527ce554..a7570be92e76f5 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -905,6 +905,16 @@ complex_subtype_from_string(PyTypeObject *type, PyObject *v) return result; } +/* The constructor should only accept a string as a positional argument, + * not as by the 'real' keyword. But Argument Clinic does not allow + * to distinguish between argument passed positionally and by keyword. + * So the constructor must be split into two parts: actual_complex_new() + * handles the case of no arguments and one positional argument, and calls + * complex_new(), implemented with Argument Clinic, to handle the remaining + * cases: 'real' and 'imag' arguments. This separation is well suited + * for different constructor roles: convering a string or number to a complex + * number and constructing a complex number from real and imaginary parts. + */ static PyObject * actual_complex_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) { @@ -993,6 +1003,8 @@ complex_new_impl(PyTypeObject *type, PyObject *r, PyObject *i) } PyObject *orig_r = r; + /* DEPRECATED: The call of try_complex_special_method() for the "real" + * part will be dropped after the end of the deprecation period. */ tmp = try_complex_special_method(r); if (tmp) { r = tmp; @@ -1033,6 +1045,7 @@ complex_new_impl(PyTypeObject *type, PyObject *r, PyObject *i) both be treated as numbers, and the constructor should return a complex number equal to (real + imag*1j). + The following is DEPRECATED: Note that we do NOT assume the input to already be in canonical form; the "real" and "imag" parts might themselves be complex numbers, which slightly complicates the code below. */ From 14598f0d567a8903c090ea77e100316a9b332997 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 29 May 2024 10:22:06 +0300 Subject: [PATCH 7/8] Polishing. --- Objects/complexobject.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Objects/complexobject.c b/Objects/complexobject.c index a7570be92e76f5..9182542dd9b968 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -1056,6 +1056,8 @@ complex_new_impl(PyTypeObject *type, PyObject *r, PyObject *i) cr = ((PyComplexObject*)r)->cval; cr_is_complex = 1; if (own_r) { + /* r was a newly created complex number, rather + than the original "real" argument. */ Py_DECREF(r); } nbr = Py_TYPE(orig_r)->tp_as_number; @@ -1074,11 +1076,7 @@ complex_new_impl(PyTypeObject *type, PyObject *r, PyObject *i) nothing in the imaginary direction. Just treat it as a double. */ tmp = PyNumber_Float(r); - if (own_r) { - /* r was a newly created complex number, rather - than the original "real" argument. */ - Py_DECREF(r); - } + assert(!own_r); if (tmp == NULL) return NULL; assert(PyFloat_Check(tmp)); From 90029cf3e3314a56f404df1716582c0b4a780668 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 30 May 2024 22:42:19 +0300 Subject: [PATCH 8/8] Rename Mock to With. --- Lib/test/test_complex.py | 68 ++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index 0d7e45d6bd1ce2..3623d081046c06 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -21,13 +21,13 @@ (1, 0+0j), ) -class MockIndex: +class WithIndex: def __init__(self, value): self.value = value def __index__(self): return self.value -class MockFloat: +class WithFloat: def __init__(self, value): self.value = value def __float__(self): @@ -36,7 +36,7 @@ def __float__(self): class ComplexSubclass(complex): pass -class MockComplex: +class WithComplex: def __init__(self, value): self.value = value def __complex__(self): @@ -372,7 +372,7 @@ def check(z, x, y): check(complex(4.25+0j), 4.25, 0.0) check(complex(4.25+0.5j), 4.25, 0.5) check(complex(ComplexSubclass(4.25+0.5j)), 4.25, 0.5) - check(complex(MockComplex(4.25+0.5j)), 4.25, 0.5) + check(complex(WithComplex(4.25+0.5j)), 4.25, 0.5) check(complex(1, 10), 1.0, 10.0) check(complex(1, 10.0), 1.0, 10.0) @@ -389,8 +389,8 @@ def check(z, x, y): "argument 'real' must be a real number, not .*ComplexSubclass"): check(complex(ComplexSubclass(4.25+0j), 0), 4.25, 0.0) with self.assertWarnsRegex(DeprecationWarning, - "argument 'real' must be a real number, not .*MockComplex"): - check(complex(MockComplex(4.25+0j), 0), 4.25, 0.0) + "argument 'real' must be a real number, not .*WithComplex"): + check(complex(WithComplex(4.25+0j), 0), 4.25, 0.0) with self.assertWarnsRegex(DeprecationWarning, "argument 'real' must be a real number, not complex"): check(complex(4.25j, 0), 0.0, 4.25) @@ -404,8 +404,8 @@ def check(z, x, y): "argument 'imag' must be a real number, not .*ComplexSubclass"): check(complex(0, ComplexSubclass(4.25+0j)), 0.0, 4.25) with self.assertRaisesRegex(TypeError, - "argument 'imag' must be a real number, not .*MockComplex"): - complex(0, MockComplex(4.25+0j)) + "argument 'imag' must be a real number, not .*WithComplex"): + complex(0, WithComplex(4.25+0j)) with self.assertWarnsRegex(DeprecationWarning, "argument 'imag' must be a real number, not complex"): check(complex(0.0, 4.25j), -4.25, 0.0) @@ -467,13 +467,13 @@ def check(z, x, y): "argument 'imag' must be a real number, not str", complex, 0, '1') - self.assertRaises(TypeError, complex, MockComplex(1.5)) - self.assertRaises(TypeError, complex, MockComplex(1)) - self.assertRaises(TypeError, complex, MockComplex(None)) - self.assertRaises(TypeError, complex, MockComplex(4.25+0j), object()) - self.assertRaises(TypeError, complex, MockComplex(1.5), object()) - self.assertRaises(TypeError, complex, MockComplex(1), object()) - self.assertRaises(TypeError, complex, MockComplex(None), object()) + self.assertRaises(TypeError, complex, WithComplex(1.5)) + self.assertRaises(TypeError, complex, WithComplex(1)) + self.assertRaises(TypeError, complex, WithComplex(None)) + self.assertRaises(TypeError, complex, WithComplex(4.25+0j), object()) + self.assertRaises(TypeError, complex, WithComplex(1.5), object()) + self.assertRaises(TypeError, complex, WithComplex(1), object()) + self.assertRaises(TypeError, complex, WithComplex(None), object()) class EvilExc(Exception): pass @@ -484,25 +484,25 @@ def __complex__(self): self.assertRaises(EvilExc, complex, evilcomplex()) - check(complex(MockFloat(4.25)), 4.25, 0.0) - check(complex(MockFloat(4.25), 1.5), 4.25, 1.5) - check(complex(1.5, MockFloat(4.25)), 1.5, 4.25) - self.assertRaises(TypeError, complex, MockFloat(42)) - self.assertRaises(TypeError, complex, MockFloat(42), 1.5) - self.assertRaises(TypeError, complex, 1.5, MockFloat(42)) - self.assertRaises(TypeError, complex, MockFloat(None)) - self.assertRaises(TypeError, complex, MockFloat(None), 1.5) - self.assertRaises(TypeError, complex, 1.5, MockFloat(None)) - - check(complex(MockIndex(42)), 42.0, 0.0) - check(complex(MockIndex(42), 1.5), 42.0, 1.5) - check(complex(1.5, MockIndex(42)), 1.5, 42.0) - self.assertRaises(OverflowError, complex, MockIndex(2**2000)) - self.assertRaises(OverflowError, complex, MockIndex(2**2000), 1.5) - self.assertRaises(OverflowError, complex, 1.5, MockIndex(2**2000)) - self.assertRaises(TypeError, complex, MockIndex(None)) - self.assertRaises(TypeError, complex, MockIndex(None), 1.5) - self.assertRaises(TypeError, complex, 1.5, MockIndex(None)) + check(complex(WithFloat(4.25)), 4.25, 0.0) + check(complex(WithFloat(4.25), 1.5), 4.25, 1.5) + check(complex(1.5, WithFloat(4.25)), 1.5, 4.25) + self.assertRaises(TypeError, complex, WithFloat(42)) + self.assertRaises(TypeError, complex, WithFloat(42), 1.5) + self.assertRaises(TypeError, complex, 1.5, WithFloat(42)) + self.assertRaises(TypeError, complex, WithFloat(None)) + self.assertRaises(TypeError, complex, WithFloat(None), 1.5) + self.assertRaises(TypeError, complex, 1.5, WithFloat(None)) + + check(complex(WithIndex(42)), 42.0, 0.0) + check(complex(WithIndex(42), 1.5), 42.0, 1.5) + check(complex(1.5, WithIndex(42)), 1.5, 42.0) + self.assertRaises(OverflowError, complex, WithIndex(2**2000)) + self.assertRaises(OverflowError, complex, WithIndex(2**2000), 1.5) + self.assertRaises(OverflowError, complex, 1.5, WithIndex(2**2000)) + self.assertRaises(TypeError, complex, WithIndex(None)) + self.assertRaises(TypeError, complex, WithIndex(None), 1.5) + self.assertRaises(TypeError, complex, 1.5, WithIndex(None)) class MyInt: def __int__(self):