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
|
||||
: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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -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 {
|
||||
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 */
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue