Skip to content

Commit

Permalink
pythongh-109218: Deprecate weird cases in the complex() constructor
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
serhiy-storchaka committed May 27, 2024
1 parent 59630f9 commit 9ec7587
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 59 deletions.
4 changes: 4 additions & 0 deletions Doc/library/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 :issue:`109218`.)


Removed
Expand Down
109 changes: 74 additions & 35 deletions Lib/test/test_complex.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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")
Expand All @@ -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")
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)
Expand Down
5 changes: 4 additions & 1 deletion Lib/test/test_fractions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'))
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Loading

0 comments on commit 9ec7587

Please sign in to comment.