Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
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
4 changes: 4 additions & 0 deletions Doc/library/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,8 @@ are always available. They are listed here in alphabetical order.
to :meth:`~object.__float__`. If :meth:`!__float__` is not defined then it falls back
to :meth:`~object.__index__`.

See also :meth:`complex.from_number` which only accept single numeric argument.
Comment thread
serhiy-storchaka marked this conversation as resolved.
Outdated

.. note::

When converting from a string, the string must not contain whitespace
Expand Down Expand Up @@ -684,6 +686,8 @@ are always available. They are listed here in alphabetical order.
``x.__float__()``. If :meth:`~object.__float__` is not defined then it falls back
to :meth:`~object.__index__`.

See also :meth:`float.from_number` which only accept numeric argument.
Comment thread
serhiy-storchaka marked this conversation as resolved.
Outdated

If no argument is given, ``0.0`` is returned.

Examples::
Expand Down
36 changes: 36 additions & 0 deletions Doc/library/stdtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,23 @@ Additional Methods on Float
The float type implements the :class:`numbers.Real` :term:`abstract base
class`. float also has the following additional methods.

.. classmethod:: float.from_number(x)

Class method to return a floating point number constructed from a number *x*.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super-nitpick: I prefer the spelling floating-point number, with a hyphen (the "floating-point" part acts as a compound adjective). I think that's what we mostly use throughout the docs (though no doubt there are exceptions).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This descriptions was simply copied from the float constructor description.

"floating-point" occurs 118 time, "floating point" occurs at least 159 times (in several cases it is split between lines, so it is not easy to get accurate number).

So for now I leave "floating point" for consistency with the constructor description. We will solve this in a separate issue.


If the argument is an integer or a floating point number, a
floating point number with the same value (within Python's floating point
precision) is returned. If the argument is outside the range of a Python
float, an :exc:`OverflowError` will be raised.

For a general Python object ``x``, ``float.from_number(x)`` delegates to
``x.__float__()``.
If :meth:`~object.__float__` is not defined then it falls back
to :meth:`~object.__index__`.

.. versionadded:: 3.13


.. method:: float.as_integer_ratio()

Return a pair of integers whose ratio is exactly equal to the
Expand Down Expand Up @@ -702,6 +719,25 @@ hexadecimal string representing the same number::
'0x1.d380000000000p+11'


Additional Methods on Complex
-----------------------------

The :class:`!complex` type implements the :class:`numbers.Complex`
:term:`abstract base class`.
:class:`!complex` also has the following additional methods.

.. classmethod:: complex.from_number(x)

Class method to convert a number to a complex number.

For a general Python object ``x``, ``complex.from_number(x)`` delegates to
``x.__complex__()``. If :meth:`~object.__complex__` is not defined then it falls back
to :meth:`~object.__float__`. If :meth:`!__float__` is not defined then it falls back
to :meth:`~object.__index__`.

.. versionadded:: 3.13


.. _numeric-hash:

Hashing of numeric types
Expand Down
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ New Features
Other Language Changes
======================

* Added class methods :meth:`float.from_number` and :meth:`complex.from_number`
to convert a number to :class:`float` or :class:`complex` type correspondingly.
They raise error if the argument is a string.
Comment thread
serhiy-storchaka marked this conversation as resolved.
Outdated
(Contributed by Serhiy Storchaka in :gh:`84978`.)

* Allow the *count* argument of :meth:`str.replace` to be a keyword.
(Contributed by Hugo van Kemenade in :gh:`106487`.)

Expand Down
117 changes: 80 additions & 37 deletions Lib/test/test_complex.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,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 @@ -306,14 +342,11 @@ 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(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 @@ -360,8 +393,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 @@ -443,33 +475,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 Down Expand Up @@ -508,24 +524,22 @@ class complex_subclass(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 All @@ -547,6 +561,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 @@ -33,6 +33,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 @@ -182,10 +204,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 @@ -207,45 +225,29 @@ 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'):
Expand Down Expand Up @@ -278,6 +280,37 @@ def __new__(cls, arg, newarg=None):
self.assertEqual(float(u), 2.5)
self.assertEqual(u.newarg, 3)

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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add class methods :meth:`float.from_number` and :meth:`complex.from_number`.
Loading