mirror of https://github.com/python/cpython
gh-109218: Deprecate weird cases in the complex() constructor (GH-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.
This commit is contained in:
parent
deda85717b
commit
ef01e95ae3
|
@ -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
|
Falls back to :meth:`~object.__index__` if :meth:`~object.__complex__` and
|
||||||
:meth:`~object.__float__` are not defined.
|
: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)
|
.. function:: delattr(object, name)
|
||||||
|
|
||||||
|
|
|
@ -103,6 +103,10 @@ Optimizations
|
||||||
Deprecated
|
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
|
Removed
|
||||||
|
|
|
@ -382,25 +382,53 @@ class ComplexTest(unittest.TestCase):
|
||||||
check(complex(1.0, 10.0), 1.0, 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, 0.5), 4.25, 0.5)
|
||||||
|
|
||||||
check(complex(4.25+0j, 0), 4.25, 0.0)
|
with self.assertWarnsRegex(DeprecationWarning,
|
||||||
check(complex(ComplexSubclass(4.25+0j), 0), 4.25, 0.0)
|
"argument 'real' must be a real number, not complex"):
|
||||||
check(complex(WithComplex(4.25+0j), 0), 4.25, 0.0)
|
check(complex(4.25+0j, 0), 4.25, 0.0)
|
||||||
check(complex(4.25j, 0), 0.0, 4.25)
|
with self.assertWarnsRegex(DeprecationWarning,
|
||||||
check(complex(0j, 4.25), 0.0, 4.25)
|
"argument 'real' must be a real number, not .*ComplexSubclass"):
|
||||||
check(complex(0, 4.25+0j), 0.0, 4.25)
|
check(complex(ComplexSubclass(4.25+0j), 0), 4.25, 0.0)
|
||||||
check(complex(0, ComplexSubclass(4.25+0j)), 0.0, 4.25)
|
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,
|
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))
|
complex(0, WithComplex(4.25+0j))
|
||||||
check(complex(0.0, 4.25j), -4.25, 0.0)
|
with self.assertWarnsRegex(DeprecationWarning,
|
||||||
check(complex(4.25+0j, 0j), 4.25, 0.0)
|
"argument 'imag' must be a real number, not complex"):
|
||||||
check(complex(4.25j, 0j), 0.0, 4.25)
|
check(complex(0.0, 4.25j), -4.25, 0.0)
|
||||||
check(complex(0j, 4.25+0j), 0.0, 4.25)
|
with self.assertWarnsRegex(DeprecationWarning,
|
||||||
check(complex(0j, 4.25j), -4.25, 0.0)
|
"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), 4.25, 0.0)
|
||||||
check(complex(real=4.25+0j), 4.25, 0.0)
|
with self.assertWarnsRegex(DeprecationWarning,
|
||||||
check(complex(real=4.25+1.5j), 4.25, 1.5)
|
"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(imag=1.5), 0.0, 1.5)
|
||||||
check(complex(real=4.25, imag=1.5), 4.25, 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(complex(4.25, imag=1.5), 4.25, 1.5)
|
||||||
|
@ -420,22 +448,22 @@ class ComplexTest(unittest.TestCase):
|
||||||
del c, c2
|
del c, c2
|
||||||
|
|
||||||
self.assertRaisesRegex(TypeError,
|
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, {})
|
complex, {})
|
||||||
self.assertRaisesRegex(TypeError,
|
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)
|
complex, None)
|
||||||
self.assertRaisesRegex(TypeError,
|
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)
|
complex, {1:2}, 0)
|
||||||
self.assertRaisesRegex(TypeError,
|
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)
|
complex, '1', 0)
|
||||||
self.assertRaisesRegex(TypeError,
|
self.assertRaisesRegex(TypeError,
|
||||||
"second argument must be a number, not 'dict'",
|
"argument 'imag' must be a real number, not dict",
|
||||||
complex, 0, {1:2})
|
complex, 0, {1:2})
|
||||||
self.assertRaisesRegex(TypeError,
|
self.assertRaisesRegex(TypeError,
|
||||||
"second arg can't be a string",
|
"argument 'imag' must be a real number, not str",
|
||||||
complex, 0, '1')
|
complex, 0, '1')
|
||||||
|
|
||||||
self.assertRaises(TypeError, complex, WithComplex(1.5))
|
self.assertRaises(TypeError, complex, WithComplex(1.5))
|
||||||
|
|
|
@ -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, 2), Polar(F(6, 1), 2))
|
||||||
self.assertTypedEquals(F(3, 2) * Polar(4.0, 2), Polar(6.0, 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) * 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.assertRaises(TypeError, operator.mul, Polar(4, 2), F(3, 2))
|
||||||
self.assertTypedEquals(Rect(4, 3) * F(3, 2), 6.0 + 4.5j)
|
self.assertTypedEquals(Rect(4, 3) * F(3, 2), 6.0 + 4.5j)
|
||||||
self.assertEqual(F(3, 2) * SymbolicComplex('X'), SymbolicComplex('3/2 * X'))
|
self.assertEqual(F(3, 2) * SymbolicComplex('X'), SymbolicComplex('3/2 * X'))
|
||||||
|
|
|
@ -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.
|
|
@ -894,8 +894,8 @@ complex_subtype_from_string(PyTypeObject *type, PyObject *v)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
PyErr_Format(PyExc_TypeError,
|
PyErr_Format(PyExc_TypeError,
|
||||||
"complex() argument must be a string or a number, not '%.200s'",
|
"complex() argument must be a string or a number, not %T",
|
||||||
Py_TYPE(v)->tp_name);
|
v);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -905,6 +905,77 @@ complex_subtype_from_string(PyTypeObject *type, PyObject *v)
|
||||||
return result;
|
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]
|
/*[clinic input]
|
||||||
@classmethod
|
@classmethod
|
||||||
complex.__new__ as complex_new
|
complex.__new__ as complex_new
|
||||||
|
@ -933,32 +1004,10 @@ complex_new_impl(PyTypeObject *type, PyObject *r, PyObject *i)
|
||||||
if (r == NULL) {
|
if (r == NULL) {
|
||||||
r = _PyLong_GetZero();
|
r = _PyLong_GetZero();
|
||||||
}
|
}
|
||||||
|
PyObject *orig_r = r;
|
||||||
|
|
||||||
/* Special-case for a single argument when type(arg) is complex. */
|
/* DEPRECATED: The call of try_complex_special_method() for the "real"
|
||||||
if (PyComplex_CheckExact(r) && i == NULL &&
|
* part will be dropped after the end of the deprecation period. */
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
tmp = try_complex_special_method(r);
|
tmp = try_complex_special_method(r);
|
||||||
if (tmp) {
|
if (tmp) {
|
||||||
r = 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)))
|
(nbr->nb_float == NULL && nbr->nb_index == NULL && !PyComplex_Check(r)))
|
||||||
{
|
{
|
||||||
PyErr_Format(PyExc_TypeError,
|
PyErr_Format(PyExc_TypeError,
|
||||||
"complex() first argument must be a string or a number, "
|
"complex() argument 'real' must be a real number, not %T",
|
||||||
"not '%.200s'",
|
r);
|
||||||
Py_TYPE(r)->tp_name);
|
|
||||||
if (own_r) {
|
if (own_r) {
|
||||||
Py_DECREF(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)))
|
(nbi->nb_float == NULL && nbi->nb_index == NULL && !PyComplex_Check(i)))
|
||||||
{
|
{
|
||||||
PyErr_Format(PyExc_TypeError,
|
PyErr_Format(PyExc_TypeError,
|
||||||
"complex() second argument must be a number, "
|
"complex() argument 'imag' must be a real number, not %T",
|
||||||
"not '%.200s'",
|
i);
|
||||||
Py_TYPE(i)->tp_name);
|
|
||||||
if (own_r) {
|
if (own_r) {
|
||||||
Py_DECREF(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
|
both be treated as numbers, and the constructor should return a
|
||||||
complex number equal to (real + imag*1j).
|
complex number equal to (real + imag*1j).
|
||||||
|
|
||||||
|
The following is DEPRECATED:
|
||||||
Note that we do NOT assume the input to already be in canonical
|
Note that we do NOT assume the input to already be in canonical
|
||||||
form; the "real" and "imag" parts might themselves be complex
|
form; the "real" and "imag" parts might themselves be complex
|
||||||
numbers, which slightly complicates the code below. */
|
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 = ((PyComplexObject*)r)->cval;
|
||||||
cr_is_complex = 1;
|
cr_is_complex = 1;
|
||||||
if (own_r) {
|
if (own_r) {
|
||||||
|
/* r was a newly created complex number, rather
|
||||||
|
than the original "real" argument. */
|
||||||
Py_DECREF(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 {
|
else {
|
||||||
/* The "real" part really is entirely real, and contributes
|
/* The "real" part really is entirely real, and contributes
|
||||||
nothing in the imaginary direction.
|
nothing in the imaginary direction.
|
||||||
Just treat it as a double. */
|
Just treat it as a double. */
|
||||||
tmp = PyNumber_Float(r);
|
tmp = PyNumber_Float(r);
|
||||||
if (own_r) {
|
assert(!own_r);
|
||||||
/* r was a newly created complex number, rather
|
|
||||||
than the original "real" argument. */
|
|
||||||
Py_DECREF(r);
|
|
||||||
}
|
|
||||||
if (tmp == NULL)
|
if (tmp == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
assert(PyFloat_Check(tmp));
|
assert(PyFloat_Check(tmp));
|
||||||
|
@ -1035,6 +1091,11 @@ complex_new_impl(PyTypeObject *type, PyObject *r, PyObject *i)
|
||||||
ci.real = cr.imag;
|
ci.real = cr.imag;
|
||||||
}
|
}
|
||||||
else if (PyComplex_Check(i)) {
|
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 = ((PyComplexObject*)i)->cval;
|
||||||
ci_is_complex = 1;
|
ci_is_complex = 1;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1134,6 +1195,6 @@ PyTypeObject PyComplex_Type = {
|
||||||
0, /* tp_dictoffset */
|
0, /* tp_dictoffset */
|
||||||
0, /* tp_init */
|
0, /* tp_init */
|
||||||
PyType_GenericAlloc, /* tp_alloc */
|
PyType_GenericAlloc, /* tp_alloc */
|
||||||
complex_new, /* tp_new */
|
actual_complex_new, /* tp_new */
|
||||||
PyObject_Del, /* tp_free */
|
PyObject_Del, /* tp_free */
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue