8000 gh-109218: Deprecate weird cases in the complex() constructor by serhiy-storchaka · Pull Request #119620 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

gh-109218: Deprecate weird cases in the complex() constructor #119620

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
gh-109218: Deprecate weird cases in the complex() constructor
* 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
commit 9035969533a4c7def622e142dd8606d0a1291538
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 :gh:`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