summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/functions.rst4
-rw-r--r--Doc/whatsnew/3.14.rst4
-rw-r--r--Lib/test/test_complex.py70
-rw-r--r--Lib/test/test_fractions.py5
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2024-05-27-19-13-49.gh-issue-109218.-sdDg0.rst3
-rw-r--r--Objects/complexobject.c139
6 files changed, 164 insertions, 61 deletions
diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst
index c07b104..7291461 100644
--- a/Doc/library/functions.rst
+++ b/Doc/library/functions.rst
@@ -449,6 +449,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 8c37825..d443cf9 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 f29b7d3..fb510ca 100644
--- a/Lib/test/test_complex.py
+++ b/Lib/test/test_complex.py
@@ -382,25 +382,53 @@ class ComplexTest(unittest.TestCase):
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(WithComplex(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.assertWarnsRegex(DeprecationWarning,
+ "argument 'real' must be a real number, not complex"):
+ check(complex(4.25+0j, 0), 4.25, 0.0)
+ with self.assertWarnsRegex(DeprecationWarning,
+ "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 .*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)
+ with self.assertWarnsRegex(DeprecationWarning,
+ "argument 'real' must be a real number, not complex"):
+ check(complex(0j, 4.25), 0.0, 4.25)
+ with self.assertWarnsRegex(DeprecationWarning,
+ "argument 'imag' must be a real number, not complex"):
+ check(complex(0, 4.25+0j), 0.0, 4.25)
+ with self.assertWarnsRegex(DeprecationWarning,
+ "argument 'imag' must be a real number, not .*ComplexSubclass"):
+ check(complex(0, ComplexSubclass(4.25+0j)), 0.0, 4.25)
with self.assertRaisesRegex(TypeError,
- "second argument must be a number, not 'WithComplex'"):
+ "argument 'imag' must be a real number, not .*WithComplex"):
complex(0, WithComplex(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)
+ with self.assertWarnsRegex(DeprecationWarning,
+ "argument 'imag' must be a real number, not complex"):
+ check(complex(0.0, 4.25j), -4.25, 0.0)
+ with self.assertWarnsRegex(DeprecationWarning,
+ "argument 'real' must be a real number, not complex"):
+ check(complex(4.25+0j, 0j), 4.25, 0.0)
+ with self.assertWarnsRegex(DeprecationWarning,
+ "argument 'real' must be a real number, not complex"):
+ check(complex(4.25j, 0j), 0.0, 4.25)
+ with self.assertWarnsRegex(DeprecationWarning,
+ "argument 'real' must be a real number, not complex"):
+ check(complex(0j, 4.25+0j), 0.0, 4.25)
+ with self.assertWarnsRegex(DeprecationWarning,
+ "argument 'real' must be a real number, not complex"):
+ 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)
+ 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)
@@ -420,22 +448,22 @@ class ComplexTest(unittest.TestCase):
del c, c2
self.assertRaisesRegex(TypeError,
- "first argument must be a string or a number, not 'dict'",
+ "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'",
+ "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'",
+ "argument 'real' must be a real number, not dict",
complex, {1:2}, 0)
self.assertRaisesRegex(TypeError,
- "can't take second arg if first is a string",
+ "argument 'real' must be a real number, not str",
complex, '1', 0)
self.assertRaisesRegex(TypeError,
- "second argument must be a number, not 'dict'",
+ "argument 'imag' must be a real number, not dict",
complex, 0, {1:2})
self.assertRaisesRegex(TypeError,
- "second arg can't be a string",
+ "argument 'imag' must be a real number, not str",
complex, 0, '1')
self.assertRaises(TypeError, complex, WithComplex(1.5))
diff --git a/Lib/test/test_fractions.py b/Lib/test/test_fractions.py
index 3648a89..28607ee 100644
--- a/Lib/test/test_fractions.py
+++ b/Lib/test/test_fractions.py
@@ -806,7 +806,10 @@ class FractionTest(unittest.TestCase):
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 0000000..db76217
--- /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 17ee437..59c84f1 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,77 @@ 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)
+{
+ PyObject *res = NULL;
+ PyNumberMethods *nbr;
+
+ 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);
+ }
+ PyObject *tmp = try_complex_special_method(arg);
+ if (tmp) {
+ 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;
+ }
+ 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. */
+ 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);
+ }
+ return res;
+}
+
/*[clinic input]
@classmethod
complex.__new__ as complex_new
@@ -933,32 +1004,10 @@ 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) {
- /* 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);
- }
- 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;
- }
-
+ /* 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;
@@ -973,9 +1022,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);
}
@@ -987,9 +1035,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);
}
@@ -1001,6 +1048,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. */
@@ -1011,19 +1059,27 @@ 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;
+ 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
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));
@@ -1035,6 +1091,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 {
@@ -1134,6 +1195,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 */
};