Skip to content

Commit a6d8d0f

Browse files
serhiy-storchakaestyxx
authored andcommitted
pythongh-109218: Deprecate weird cases in the complex() constructor (pythonGH-119620)
* 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.
1 parent 2825a97 commit a6d8d0f

File tree

6 files changed

+164
-61
lines changed

6 files changed

+164
-61
lines changed

Doc/library/functions.rst

+4
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,10 @@ are always available. They are listed here in alphabetical order.
449449
Falls back to :meth:`~object.__index__` if :meth:`~object.__complex__` and
450450
:meth:`~object.__float__` are not defined.
451451

452+
.. deprecated:: 3.14
453+
Passing a complex number as the *real* or *imag* argument is now
454+
deprecated; it should only be passed as a single positional argument.
455+
452456

453457
.. function:: delattr(object, name)
454458

Doc/whatsnew/3.14.rst

+4
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ Optimizations
103103
Deprecated
104104
==========
105105

106+
* Passing a complex number as the *real* or *imag* argument in the
107+
:func:`complex` constructor is now deprecated; it should only be passed
108+
as a single positional argument.
109+
(Contributed by Serhiy Storchaka in :gh:`109218`.)
106110

107111

108112
Removed

Lib/test/test_complex.py

+49-21
Original file line numberDiff line numberDiff line change
@@ -382,25 +382,53 @@ def check(z, x, y):
382382
check(complex(1.0, 10.0), 1.0, 10.0)
383383
check(complex(4.25, 0.5), 4.25, 0.5)
384384

385-
check(complex(4.25+0j, 0), 4.25, 0.0)
386-
check(complex(ComplexSubclass(4.25+0j), 0), 4.25, 0.0)
387-
check(complex(WithComplex(4.25+0j), 0), 4.25, 0.0)
388-
check(complex(4.25j, 0), 0.0, 4.25)
389-
check(complex(0j, 4.25), 0.0, 4.25)
390-
check(complex(0, 4.25+0j), 0.0, 4.25)
391-
check(complex(0, ComplexSubclass(4.25+0j)), 0.0, 4.25)
385+
with self.assertWarnsRegex(DeprecationWarning,
386+
"argument 'real' must be a real number, not complex"):
387+
check(complex(4.25+0j, 0), 4.25, 0.0)
388+
with self.assertWarnsRegex(DeprecationWarning,
389+
"argument 'real' must be a real number, not .*ComplexSubclass"):
390+
check(complex(ComplexSubclass(4.25+0j), 0), 4.25, 0.0)
391+
with self.assertWarnsRegex(DeprecationWarning,
392+
"argument 'real' must be a real number, not .*WithComplex"):
393+
check(complex(WithComplex(4.25+0j), 0), 4.25, 0.0)
394+
with self.assertWarnsRegex(DeprecationWarning,
395+
"argument 'real' must be a real number, not complex"):
396+
check(complex(4.25j, 0), 0.0, 4.25)
397+
with self.assertWarnsRegex(DeprecationWarning,
398+
"argument 'real' must be a real number, not complex"):
399+
check(complex(0j, 4.25), 0.0, 4.25)
400+
with self.assertWarnsRegex(DeprecationWarning,
401+
"argument 'imag' must be a real number, not complex"):
402+
check(complex(0, 4.25+0j), 0.0, 4.25)
403+
with self.assertWarnsRegex(DeprecationWarning,
404+
"argument 'imag' must be a real number, not .*ComplexSubclass"):
405+
check(complex(0, ComplexSubclass(4.25+0j)), 0.0, 4.25)
392406
with self.assertRaisesRegex(TypeError,
393-
"second argument must be a number, not 'WithComplex'"):
407+
"argument 'imag' must be a real number, not .*WithComplex"):
394408
complex(0, WithComplex(4.25+0j))
395-
check(complex(0.0, 4.25j), -4.25, 0.0)
396-
check(complex(4.25+0j, 0j), 4.25, 0.0)
397-
check(complex(4.25j, 0j), 0.0, 4.25)
398-
check(complex(0j, 4.25+0j), 0.0, 4.25)
399-
check(complex(0j, 4.25j), -4.25, 0.0)
409+
with self.assertWarnsRegex(DeprecationWarning,
410+
"argument 'imag' must be a real number, not complex"):
411+
check(complex(0.0, 4.25j), -4.25, 0.0)
412+
with self.assertWarnsRegex(DeprecationWarning,
413+
"argument 'real' must be a real number, not complex"):
414+
check(complex(4.25+0j, 0j), 4.25, 0.0)
415+
with self.assertWarnsRegex(DeprecationWarning,
416+
"argument 'real' must be a real number, not complex"):
417+
check(complex(4.25j, 0j), 0.0, 4.25)
418+
with self.assertWarnsRegex(DeprecationWarning,
419+
"argument 'real' must be a real number, not complex"):
420+
check(complex(0j, 4.25+0j), 0.0, 4.25)
421+
with self.assertWarnsRegex(DeprecationWarning,
422+
"argument 'real' must be a real number, not complex"):
423+
check(complex(0j, 4.25j), -4.25, 0.0)
400424

401425
check(complex(real=4.25), 4.25, 0.0)
402-
check(complex(real=4.25+0j), 4.25, 0.0)
403-
check(complex(real=4.25+1.5j), 4.25, 1.5)
426+
with self.assertWarnsRegex(DeprecationWarning,
427+
"argument 'real' must be a real number, not complex"):
428+
check(complex(real=4.25+0j), 4.25, 0.0)
429+
with self.assertWarnsRegex(DeprecationWarning,
430+
"argument 'real' must be a real number, not complex"):
431+
check(complex(real=4.25+1.5j), 4.25, 1.5)
404432
check(complex(imag=1.5), 0.0, 1.5)
405433
check(complex(real=4.25, imag=1.5), 4.25, 1.5)
406434
check(complex(4.25, imag=1.5), 4.25, 1.5)
@@ -420,22 +448,22 @@ def check(z, x, y):
420448
del c, c2
421449

422450
self.assertRaisesRegex(TypeError,
423-
"first argument must be a string or a number, not 'dict'",
451+
"argument must be a string or a number, not dict",
424452
complex, {})
425453
self.assertRaisesRegex(TypeError,
426-
"first argument must be a string or a number, not 'NoneType'",
454+
"argument must be a string or a number, not NoneType",
427455
complex, None)
428456
self.assertRaisesRegex(TypeError,
429-
"first argument must be a string or a number, not 'dict'",
457+
"argument 'real' must be a real number, not dict",
430458
complex, {1:2}, 0)
431459
self.assertRaisesRegex(TypeError,
432-
"can't take second arg if first is a string",
460+
"argument 'real' must be a real number, not str",
433461
complex, '1', 0)
434462
self.assertRaisesRegex(TypeError,
435-
"second argument must be a number, not 'dict'",
463+
"argument 'imag' must be a real number, not dict",
436464
complex, 0, {1:2})
437465
self.assertRaisesRegex(TypeError,
438-
"second arg can't be a string",
466+
"argument 'imag' must be a real number, not str",
439467
complex, 0, '1')
440468

441469
self.assertRaises(TypeError, complex, WithComplex(1.5))

Lib/test/test_fractions.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -806,7 +806,10 @@ def testMixedMultiplication(self):
806806
self.assertTypedEquals(F(3, 2) * Polar(4, 2), Polar(F(6, 1), 2))
807807
self.assertTypedEquals(F(3, 2) * Polar(4.0, 2), Polar(6.0, 2))
808808
self.assertTypedEquals(F(3, 2) * Rect(4, 3), Rect(F(6, 1), F(9, 2)))
809-
self.assertTypedEquals(F(3, 2) * RectComplex(4, 3), RectComplex(6.0+0j, 4.5+0j))
809+
with self.assertWarnsRegex(DeprecationWarning,
810+
"argument 'real' must be a real number, not complex"):
811+
self.assertTypedEquals(F(3, 2) * RectComplex(4, 3),
812+
RectComplex(6.0+0j, 4.5+0j))
810813
self.assertRaises(TypeError, operator.mul, Polar(4, 2), F(3, 2))
811814
self.assertTypedEquals(Rect(4, 3) * F(3, 2), 6.0 + 4.5j)
812815
self.assertEqual(F(3, 2) * SymbolicComplex('X'), SymbolicComplex('3/2 * X'))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:func:`complex` accepts now a string only as a positional argument. Passing
2+
a complex number as the "real" or "imag" argument is deprecated; it should
3+
only be passed as a single positional argument.

Objects/complexobject.c

+100-39
Original file line numberDiff line numberDiff line change
@@ -894,8 +894,8 @@ complex_subtype_from_string(PyTypeObject *type, PyObject *v)
894894
}
895895
else {
896896
PyErr_Format(PyExc_TypeError,
897-
"complex() argument must be a string or a number, not '%.200s'",
898-
Py_TYPE(v)->tp_name);
897+
"complex() argument must be a string or a number, not %T",
898+
v);
899899
return NULL;
900900
}
901901

@@ -905,6 +905,77 @@ complex_subtype_from_string(PyTypeObject *type, PyObject *v)
905905
return result;
906906
}
907907

908+
/* The constructor should only accept a string as a positional argument,
909+
* not as by the 'real' keyword. But Argument Clinic does not allow
910+
* to distinguish between argument passed positionally and by keyword.
911+
* So the constructor must be split into two parts: actual_complex_new()
912+
* handles the case of no arguments and one positional argument, and calls
913+
* complex_new(), implemented with Argument Clinic, to handle the remaining
914+
* cases: 'real' and 'imag' arguments. This separation is well suited
915+
* for different constructor roles: convering a string or number to a complex
916+
* number and constructing a complex number from real and imaginary parts.
917+
*/
918+
static PyObject *
919+
actual_complex_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
920+
{
921+
PyObject *res = NULL;
922+
PyNumberMethods *nbr;
923+
924+
if (PyTuple_GET_SIZE(args) > 1 || (kwargs != NULL && PyDict_GET_SIZE(kwargs))) {
925+
return complex_new(type, args, kwargs);
926+
}
927+
if (!PyTuple_GET_SIZE(args)) {
928+
return complex_subtype_from_doubles(type, 0, 0);
929+
}
930+
931+
PyObject *arg = PyTuple_GET_ITEM(args, 0);
932+
/* Special-case for a single argument when type(arg) is complex. */
933+
if (PyComplex_CheckExact(arg) && type == &PyComplex_Type) {
934+
/* Note that we can't know whether it's safe to return
935+
a complex *subclass* instance as-is, hence the restriction
936+
to exact complexes here. If either the input or the
937+
output is a complex subclass, it will be handled below
938+
as a non-orthogonal vector. */
939+
return Py_NewRef(arg);
940+
}
941+
if (PyUnicode_Check(arg)) {
942+
return complex_subtype_from_string(type, arg);
943+
}
944+
PyObject *tmp = try_complex_special_method(arg);
945+
if (tmp) {
946+
Py_complex c = ((PyComplexObject*)tmp)->cval;
947+
res = complex_subtype_from_doubles(type, c.real, c.imag);
948+
Py_DECREF(tmp);
949+
}
950+
else if (PyErr_Occurred()) {
951+
return NULL;
952+
}
953+
else if (PyComplex_Check(arg)) {
954+
/* Note that if arg is of a complex subtype, we're only
955+
retaining its real & imag parts here, and the return
956+
value is (properly) of the builtin complex type. */
957+
Py_complex c = ((PyComplexObject*)arg)->cval;
958+
res = complex_subtype_from_doubles(type, c.real, c.imag);
959+
}
960+
else if ((nbr = Py_TYPE(arg)->tp_as_number) != NULL &&
961+
(nbr->nb_float != NULL || nbr->nb_index != NULL))
962+
{
963+
/* The argument really is entirely real, and contributes
964+
nothing in the imaginary direction.
965+
Just treat it as a double. */
966+
double r = PyFloat_AsDouble(arg);
967+
if (r != -1.0 || !PyErr_Occurred()) {
968+
res = complex_subtype_from_doubles(type, r, 0);
969+
}
970+
}
971+
else {
972+
PyErr_Format(PyExc_TypeError,
973+
"complex() argument must be a string or a number, not %T",
974+
arg);
975+
}
976+
return res;
977+
}
978+
908979
/*[clinic input]
909980
@classmethod
910981
complex.__new__ as complex_new
@@ -933,32 +1004,10 @@ complex_new_impl(PyTypeObject *type, PyObject *r, PyObject *i)
9331004
if (r == NULL) {
9341005
r = _PyLong_GetZero();
9351006
}
1007+
PyObject *orig_r = r;
9361008

937-
/* Special-case for a single argument when type(arg) is complex. */
938-
if (PyComplex_CheckExact(r) && i == NULL &&
939-
type == &PyComplex_Type) {
940-
/* Note that we can't know whether it's safe to return
941-
a complex *subclass* instance as-is, hence the restriction
942-
to exact complexes here. If either the input or the
943-
output is a complex subclass, it will be handled below
944-
as a non-orthogonal vector. */
945-
return Py_NewRef(r);
946-
}
947-
if (PyUnicode_Check(r)) {
948-
if (i != NULL) {
949-
PyErr_SetString(PyExc_TypeError,
950-
"complex() can't take second arg"
951-
" if first is a string");
952-
return NULL;
953-
}
954-
return complex_subtype_from_string(type, r);
955-
}
956-
if (i != NULL && PyUnicode_Check(i)) {
957-
PyErr_SetString(PyExc_TypeError,
958-
"complex() second arg can't be a string");
959-
return NULL;
960-
}
961-
1009+
/* DEPRECATED: The call of try_complex_special_method() for the "real"
1010+
* part will be dropped after the end of the deprecation period. */
9621011
tmp = try_complex_special_method(r);
9631012
if (tmp) {
9641013
r = tmp;
@@ -973,9 +1022,8 @@ complex_new_impl(PyTypeObject *type, PyObject *r, PyObject *i)
9731022
(nbr->nb_float == NULL && nbr->nb_index == NULL && !PyComplex_Check(r)))
9741023
{
9751024
PyErr_Format(PyExc_TypeError,
976-
"complex() first argument must be a string or a number, "
977-
"not '%.200s'",
978-
Py_TYPE(r)->tp_name);
1025+
"complex() argument 'real' must be a real number, not %T",
1026+
r);
9791027
if (own_r) {
9801028
Py_DECREF(r);
9811029
}
@@ -987,9 +1035,8 @@ complex_new_impl(PyTypeObject *type, PyObject *r, PyObject *i)
9871035
(nbi->nb_float == NULL && nbi->nb_index == NULL && !PyComplex_Check(i)))
9881036
{
9891037
PyErr_Format(PyExc_TypeError,
990-
"complex() second argument must be a number, "
991-
"not '%.200s'",
992-
Py_TYPE(i)->tp_name);
1038+
"complex() argument 'imag' must be a real number, not %T",
1039+
i);
9931040
if (own_r) {
9941041
Py_DECREF(r);
9951042
}
@@ -1001,6 +1048,7 @@ complex_new_impl(PyTypeObject *type, PyObject *r, PyObject *i)
10011048
both be treated as numbers, and the constructor should return a
10021049
complex number equal to (real + imag*1j).
10031050
1051+
The following is DEPRECATED:
10041052
Note that we do NOT assume the input to already be in canonical
10051053
form; the "real" and "imag" parts might themselves be complex
10061054
numbers, which slightly complicates the code below. */
@@ -1011,19 +1059,27 @@ complex_new_impl(PyTypeObject *type, PyObject *r, PyObject *i)
10111059
cr = ((PyComplexObject*)r)->cval;
10121060
cr_is_complex = 1;
10131061
if (own_r) {
1062+
/* r was a newly created complex number, rather
1063+
than the original "real" argument. */
10141064
Py_DECREF(r);
10151065
}
1066+
nbr = Py_TYPE(orig_r)->tp_as_number;
1067+
if (nbr == NULL ||
1068+
(nbr->nb_float == NULL && nbr->nb_index == NULL))
1069+
{
1070+
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
1071+
"complex() argument 'real' must be a real number, not %T",
1072+
orig_r)) {
1073+
return NULL;
1074+
}
1075+
}
10161076
}
10171077
else {
10181078
/* The "real" part really is entirely real, and contributes
10191079
nothing in the imaginary direction.
10201080
Just treat it as a double. */
10211081
tmp = PyNumber_Float(r);
1022-
if (own_r) {
1023-
/* r was a newly created complex number, rather
1024-
than the original "real" argument. */
1025-
Py_DECREF(r);
1026-
}
1082+
assert(!own_r);
10271083
if (tmp == NULL)
10281084
return NULL;
10291085
assert(PyFloat_Check(tmp));
@@ -1035,6 +1091,11 @@ complex_new_impl(PyTypeObject *type, PyObject *r, PyObject *i)
10351091
ci.real = cr.imag;
10361092
}
10371093
else if (PyComplex_Check(i)) {
1094+
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
1095+
"complex() argument 'imag' must be a real number, not %T",
1096+
i)) {
1097+
return NULL;
1098+
}
10381099
ci = ((PyComplexObject*)i)->cval;
10391100
ci_is_complex = 1;
10401101
} else {
@@ -1134,6 +1195,6 @@ PyTypeObject PyComplex_Type = {
11341195
0, /* tp_dictoffset */
11351196
0, /* tp_init */
11361197
PyType_GenericAlloc, /* tp_alloc */
1137-
complex_new, /* tp_new */
1198+
actual_complex_new, /* tp_new */
11381199
PyObject_Del, /* tp_free */
11391200
};

0 commit comments

Comments
 (0)