Skip to content
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
bpo-40801: Add float.from_number() and complex.from_number()
They are alternate constructors which only accept numbers
(including objects with special methods __float__, __complex__
and __index__), but not strings.
  • Loading branch information
serhiy-storchaka committed Jun 21, 2021
commit f3e83a47b1fbb3d564b30e90db97d9cb7733b8d6
122 changes: 80 additions & 42 deletions Lib/test/test_complex.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,42 @@

INF = float("inf")
NAN = float("nan")

class ComplexSubclass(complex):
pass

class OtherComplexSubclass(complex):
pass

class MyIndex:
def __init__(self, value):
self.value = value

def __index__(self):
return self.value

class MyInt:
def __init__(self, value):
self.value = value

def __int__(self):
return self.value

class FloatLike:
def __init__(self, value):
self.value = value

def __float__(self):
return self.value

class ComplexLike:
def __init__(self, value):
self.value = value

def __complex__(self):
return self.value


# These tests ensure that complex math does the right thing

ZERO_DIVISION = (
Expand Down Expand Up @@ -257,19 +293,11 @@ def test_conjugate(self):
self.assertClose(complex(5.3, 9.8).conjugate(), 5.3-9.8j)

def test_constructor(self):
class OS:
def __init__(self, value): self.value = value
def __complex__(self): return self.value
class NS(object):
def __init__(self, value): self.value = value
def __complex__(self): return self.value
self.assertEqual(complex(OS(1+10j)), 1+10j)
self.assertEqual(complex(NS(1+10j)), 1+10j)
self.assertRaises(TypeError, complex, OS(None))
self.assertRaises(TypeError, complex, NS(None))
self.assertEqual(complex(ComplexLike(1+10j)), 1+10j)
self.assertRaises(TypeError, complex, ComplexLike(None))
self.assertRaises(TypeError, complex, {})
self.assertRaises(TypeError, complex, NS(1.5))
self.assertRaises(TypeError, complex, NS(1))
self.assertRaises(TypeError, complex, ComplexLike(1.5))
self.assertRaises(TypeError, complex, ComplexLike(1))

self.assertAlmostEqual(complex("1+10j"), 1+10j)
self.assertAlmostEqual(complex(10), 10+0j)
Expand Down Expand Up @@ -316,8 +344,7 @@ def __complex__(self): return self.value
self.assertAlmostEqual(complex('-1e-500j'), 0.0 - 0.0j)
self.assertAlmostEqual(complex('-1e-500+1e-500j'), -0.0 + 0.0j)

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)
Expand Down Expand Up @@ -399,33 +426,17 @@ def __complex__(self):

self.assertRaises(EvilExc, complex, evilcomplex())

class float2:
def __init__(self, value):
self.value = value
def __float__(self):
return self.value

self.assertAlmostEqual(complex(float2(42.)), 42)
self.assertAlmostEqual(complex(real=float2(17.), imag=float2(23.)), 17+23j)
self.assertRaises(TypeError, complex, float2(None))

class MyIndex:
def __init__(self, value):
self.value = value
def __index__(self):
return self.value
self.assertAlmostEqual(complex(FloatLike(42.)), 42)
self.assertAlmostEqual(complex(real=FloatLike(17.), imag=FloatLike(23.)), 17+23j)
self.assertRaises(TypeError, complex, FloatLike(None))

self.assertAlmostEqual(complex(MyIndex(42)), 42.0+0.0j)
self.assertAlmostEqual(complex(123, MyIndex(42)), 123.0+42.0j)
self.assertRaises(OverflowError, complex, MyIndex(2**2000))
self.assertRaises(OverflowError, complex, 123, MyIndex(2**2000))

class MyInt:
def __int__(self):
return 42

self.assertRaises(TypeError, complex, MyInt())
self.assertRaises(TypeError, complex, 123, MyInt())
self.assertRaises(TypeError, complex, MyInt(42))
self.assertRaises(TypeError, complex, 123, MyInt(42))

class complex0(complex):
"""Test usage of __complex__() when inheriting from 'complex'"""
Expand All @@ -452,24 +463,22 @@ def __complex__(self):

@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 All @@ -485,6 +494,35 @@ def test_underscores(self):
if not any(ch in lit for ch in 'xXoObB'):
self.assertRaises(ValueError, complex, lit)

def test_from_number(self, cls=complex):
def eq(actual, expected):
self.assertEqual(actual, expected)
self.assertIs(type(actual), cls)

eq(cls.from_number(3.14), 3.14+0j)
eq(cls.from_number(3.14j), 3.14j)
eq(cls.from_number(314), 314.0+0j)
eq(cls.from_number(OtherComplexSubclass(3.14, 2.72)), 3.14+2.72j)
eq(cls.from_number(ComplexLike(3.14+2.72j)), 3.14+2.72j)
eq(cls.from_number(FloatLike(3.14)), 3.14+0j)
eq(cls.from_number(MyIndex(314)), 314.0+0j)

cNAN = complex(NAN, NAN)
x = cls.from_number(cNAN)
self.assertTrue(x != x)
self.assertIs(type(x), cls)
if cls is complex:
self.assertIs(cls.from_number(cNAN), cNAN)

self.assertRaises(TypeError, cls.from_number, '3.14')
self.assertRaises(TypeError, cls.from_number, b'3.14')
self.assertRaises(TypeError, cls.from_number, MyInt(314))
self.assertRaises(TypeError, cls.from_number, {})
self.assertRaises(TypeError, cls.from_number)

def test_from_number_subclass(self):
self.test_from_number(ComplexSubclass)

def test_hash(self):
for x in range(-30, 30):
self.assertEqual(hash(x), hash(complex(x, 0)))
Expand Down
89 changes: 61 additions & 28 deletions Lib/test/test_float.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,28 @@ class FloatSubclass(float):
class OtherFloatSubclass(float):
pass

class MyIndex:
def __init__(self, value):
self.value = value

def __index__(self):
return self.value

class MyInt:
def __init__(self, value):
self.value = value

def __int__(self):
return self.value

class FloatLike:
def __init__(self, value):
self.value = value

def __float__(self):
return self.value


class GeneralFloatCases(unittest.TestCase):

def test_float(self):
Expand Down Expand Up @@ -176,10 +198,6 @@ def test_float_with_comma(self):

def test_floatconversion(self):
# Make sure that calls to __float__() work properly
class Foo1(object):
def __float__(self):
return 42.

class Foo2(float):
def __float__(self):
return 42.
Expand All @@ -201,50 +219,65 @@ class FooStr(str):
def __float__(self):
return float(str(self)) + 1

self.assertEqual(float(Foo1()), 42.)
self.assertEqual(float(FloatLike(42.)), 42.)
self.assertEqual(float(Foo2()), 42.)
with self.assertWarns(DeprecationWarning):
self.assertEqual(float(Foo3(21)), 42.)
self.assertRaises(TypeError, float, Foo4(42))
self.assertEqual(float(FooStr('8')), 9.)

class Foo5:
def __float__(self):
return ""
self.assertRaises(TypeError, time.sleep, Foo5())
self.assertRaises(TypeError, time.sleep, FloatLike(""))

# Issue #24731
class F:
def __float__(self):
return OtherFloatSubclass(42.)
f = FloatLike(OtherFloatSubclass(42.))
with self.assertWarns(DeprecationWarning):
self.assertEqual(float(F()), 42.)
self.assertEqual(float(f), 42.)
with self.assertWarns(DeprecationWarning):
self.assertIs(type(float(F())), float)
self.assertIs(type(float(f)), float)
with self.assertWarns(DeprecationWarning):
self.assertEqual(FloatSubclass(F()), 42.)
self.assertEqual(FloatSubclass(f), 42.)
with self.assertWarns(DeprecationWarning):
self.assertIs(type(FloatSubclass(F())), FloatSubclass)

class MyIndex:
def __init__(self, value):
self.value = value
def __index__(self):
return self.value
self.assertIs(type(FloatSubclass(f)), FloatSubclass)

self.assertEqual(float(MyIndex(42)), 42.0)
self.assertRaises(OverflowError, float, MyIndex(2**2000))

class MyInt:
def __int__(self):
return 42

self.assertRaises(TypeError, float, MyInt())
self.assertRaises(TypeError, float, MyInt(42))

def test_keyword_args(self):
with self.assertRaisesRegex(TypeError, 'keyword argument'):
float(x='3.14')

def assertEqualAndType(self, actual, expected_value, expected_type):
self.assertEqual(actual, expected_value)
self.assertIs(type(actual), expected_type)

def test_from_number(self, cls=float):
def eq(actual, expected):
self.assertEqual(actual, expected)
self.assertIs(type(actual), cls)

eq(cls.from_number(3.14), 3.14)
eq(cls.from_number(314), 314.0)
eq(cls.from_number(OtherFloatSubclass(3.14)), 3.14)
eq(cls.from_number(FloatLike(3.14)), 3.14)
eq(cls.from_number(MyIndex(314)), 314.0)

x = cls.from_number(NAN)
self.assertTrue(x != x)
self.assertIs(type(x), cls)
if cls is float:
self.assertIs(cls.from_number(NAN), NAN)

self.assertRaises(TypeError, cls.from_number, '3.14')
self.assertRaises(TypeError, cls.from_number, b'3.14')
self.assertRaises(TypeError, cls.from_number, 3.14j)
self.assertRaises(TypeError, cls.from_number, MyInt(314))
self.assertRaises(TypeError, cls.from_number, {})
self.assertRaises(TypeError, cls.from_number)

def test_from_number_subclass(self):
self.test_from_number(FloatSubclass)

def test_is_integer(self):
self.assertFalse((1.1).is_integer())
self.assertTrue((1.).is_integer())
Expand Down
11 changes: 10 additions & 1 deletion Objects/clinic/complexobject.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion Objects/clinic/floatobject.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading