Skip to content

Commit 94bee45

Browse files
gh-84978: Add float.from_number() and complex.from_number() (GH-26827)
They are alternate constructors which only accept numbers (including objects with special methods __float__, __complex__ and __index__), but not strings.
1 parent 8303d32 commit 94bee45

File tree

10 files changed

+242
-46
lines changed

10 files changed

+242
-46
lines changed

Doc/library/functions.rst

+4
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,8 @@ are always available. They are listed here in alphabetical order.
438438
If one of arguments is a real number, only its real component is used in
439439
the above expressions.
440440

441+
See also :meth:`complex.from_number` which only accepts a single numeric argument.
442+
441443
If all arguments are omitted, returns ``0j``.
442444

443445
The complex type is described in :ref:`typesnumeric`.
@@ -788,6 +790,8 @@ are always available. They are listed here in alphabetical order.
788790
``x.__float__()``. If :meth:`~object.__float__` is not defined then it falls back
789791
to :meth:`~object.__index__`.
790792

793+
See also :meth:`float.from_number` which only accepts a numeric argument.
794+
791795
If no argument is given, ``0.0`` is returned.
792796

793797
The float type is described in :ref:`typesnumeric`.

Doc/library/stdtypes.rst

+36
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,23 @@ Additional Methods on Float
625625
The float type implements the :class:`numbers.Real` :term:`abstract base
626626
class`. float also has the following additional methods.
627627

628+
.. classmethod:: float.from_number(x)
629+
630+
Class method to return a floating point number constructed from a number *x*.
631+
632+
If the argument is an integer or a floating point number, a
633+
floating point number with the same value (within Python's floating point
634+
precision) is returned. If the argument is outside the range of a Python
635+
float, an :exc:`OverflowError` will be raised.
636+
637+
For a general Python object ``x``, ``float.from_number(x)`` delegates to
638+
``x.__float__()``.
639+
If :meth:`~object.__float__` is not defined then it falls back
640+
to :meth:`~object.__index__`.
641+
642+
.. versionadded:: 3.14
643+
644+
628645
.. method:: float.as_integer_ratio()
629646

630647
Return a pair of integers whose ratio is exactly equal to the
@@ -703,6 +720,25 @@ hexadecimal string representing the same number::
703720
'0x1.d380000000000p+11'
704721

705722

723+
Additional Methods on Complex
724+
-----------------------------
725+
726+
The :class:`!complex` type implements the :class:`numbers.Complex`
727+
:term:`abstract base class`.
728+
:class:`!complex` also has the following additional methods.
729+
730+
.. classmethod:: complex.from_number(x)
731+
732+
Class method to convert a number to a complex number.
733+
734+
For a general Python object ``x``, ``complex.from_number(x)`` delegates to
735+
``x.__complex__()``. If :meth:`~object.__complex__` is not defined then it falls back
736+
to :meth:`~object.__float__`. If :meth:`!__float__` is not defined then it falls back
737+
to :meth:`~object.__index__`.
738+
739+
.. versionadded:: 3.14
740+
741+
706742
.. _numeric-hash:
707743

708744
Hashing of numeric types

Doc/whatsnew/3.14.rst

+4
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ New Features
7575
Other Language Changes
7676
======================
7777

78+
* Added class methods :meth:`float.from_number` and :meth:`complex.from_number`
79+
to convert a number to :class:`float` or :class:`complex` type correspondingly.
80+
They raise an error if the argument is a string.
81+
(Contributed by Serhiy Storchaka in :gh:`84978`.)
7882

7983

8084
New Modules

Lib/test/test_complex.py

+39
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@ def __float__(self):
3636
class ComplexSubclass(complex):
3737
pass
3838

39+
class OtherComplexSubclass(complex):
40+
pass
41+
42+
class MyInt:
43+
def __init__(self, value):
44+
self.value = value
45+
46+
def __int__(self):
47+
return self.value
48+
3949
class WithComplex:
4050
def __init__(self, value):
4151
self.value = value
@@ -675,6 +685,35 @@ def test_underscores(self):
675685
if not any(ch in lit for ch in 'xXoObB'):
676686
self.assertRaises(ValueError, complex, lit)
677687

688+
def test_from_number(self, cls=complex):
689+
def eq(actual, expected):
690+
self.assertEqual(actual, expected)
691+
self.assertIs(type(actual), cls)
692+
693+
eq(cls.from_number(3.14), 3.14+0j)
694+
eq(cls.from_number(3.14j), 3.14j)
695+
eq(cls.from_number(314), 314.0+0j)
696+
eq(cls.from_number(OtherComplexSubclass(3.14, 2.72)), 3.14+2.72j)
697+
eq(cls.from_number(WithComplex(3.14+2.72j)), 3.14+2.72j)
698+
eq(cls.from_number(WithFloat(3.14)), 3.14+0j)
699+
eq(cls.from_number(WithIndex(314)), 314.0+0j)
700+
701+
cNAN = complex(NAN, NAN)
702+
x = cls.from_number(cNAN)
703+
self.assertTrue(x != x)
704+
self.assertIs(type(x), cls)
705+
if cls is complex:
706+
self.assertIs(cls.from_number(cNAN), cNAN)
707+
708+
self.assertRaises(TypeError, cls.from_number, '3.14')
709+
self.assertRaises(TypeError, cls.from_number, b'3.14')
710+
self.assertRaises(TypeError, cls.from_number, MyInt(314))
711+
self.assertRaises(TypeError, cls.from_number, {})
712+
self.assertRaises(TypeError, cls.from_number)
713+
714+
def test_from_number_subclass(self):
715+
self.test_from_number(ComplexSubclass)
716+
678717
def test_hash(self):
679718
for x in range(-30, 30):
680719
self.assertEqual(hash(x), hash(complex(x, 0)))

Lib/test/test_float.py

+61-28
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,28 @@ class FloatSubclass(float):
3232
class OtherFloatSubclass(float):
3333
pass
3434

35+
class MyIndex:
36+
def __init__(self, value):
37+
self.value = value
38+
39+
def __index__(self):
40+
return self.value
41+
42+
class MyInt:
43+
def __init__(self, value):
44+
self.value = value
45+
46+
def __int__(self):
47+
return self.value
48+
49+
class FloatLike:
50+
def __init__(self, value):
51+
self.value = value
52+
53+
def __float__(self):
54+
return self.value
55+
56+
3557
class GeneralFloatCases(unittest.TestCase):
3658

3759
def test_float(self):
@@ -181,10 +203,6 @@ def test_float_with_comma(self):
181203

182204
def test_floatconversion(self):
183205
# Make sure that calls to __float__() work properly
184-
class Foo1(object):
185-
def __float__(self):
186-
return 42.
187-
188206
class Foo2(float):
189207
def __float__(self):
190208
return 42.
@@ -206,45 +224,29 @@ class FooStr(str):
206224
def __float__(self):
207225
return float(str(self)) + 1
208226

209-
self.assertEqual(float(Foo1()), 42.)
227+
self.assertEqual(float(FloatLike(42.)), 42.)
210228
self.assertEqual(float(Foo2()), 42.)
211229
with self.assertWarns(DeprecationWarning):
212230
self.assertEqual(float(Foo3(21)), 42.)
213231
self.assertRaises(TypeError, float, Foo4(42))
214232
self.assertEqual(float(FooStr('8')), 9.)
215233

216-
class Foo5:
217-
def __float__(self):
218-
return ""
219-
self.assertRaises(TypeError, time.sleep, Foo5())
234+
self.assertRaises(TypeError, time.sleep, FloatLike(""))
220235

221236
# Issue #24731
222-
class F:
223-
def __float__(self):
224-
return OtherFloatSubclass(42.)
237+
f = FloatLike(OtherFloatSubclass(42.))
225238
with self.assertWarns(DeprecationWarning):
226-
self.assertEqual(float(F()), 42.)
239+
self.assertEqual(float(f), 42.)
227240
with self.assertWarns(DeprecationWarning):
228-
self.assertIs(type(float(F())), float)
241+
self.assertIs(type(float(f)), float)
229242
with self.assertWarns(DeprecationWarning):
230-
self.assertEqual(FloatSubclass(F()), 42.)
243+
self.assertEqual(FloatSubclass(f), 42.)
231244
with self.assertWarns(DeprecationWarning):
232-
self.assertIs(type(FloatSubclass(F())), FloatSubclass)
233-
234-
class MyIndex:
235-
def __init__(self, value):
236-
self.value = value
237-
def __index__(self):
238-
return self.value
245+
self.assertIs(type(FloatSubclass(f)), FloatSubclass)
239246

240247
self.assertEqual(float(MyIndex(42)), 42.0)
241248
self.assertRaises(OverflowError, float, MyIndex(2**2000))
242-
243-
class MyInt:
244-
def __int__(self):
245-
return 42
246-
247-
self.assertRaises(TypeError, float, MyInt())
249+
self.assertRaises(TypeError, float, MyInt(42))
248250

249251
def test_keyword_args(self):
250252
with self.assertRaisesRegex(TypeError, 'keyword argument'):
@@ -277,6 +279,37 @@ def __new__(cls, arg, newarg=None):
277279
self.assertEqual(float(u), 2.5)
278280
self.assertEqual(u.newarg, 3)
279281

282+
def assertEqualAndType(self, actual, expected_value, expected_type):
283+
self.assertEqual(actual, expected_value)
284+
self.assertIs(type(actual), expected_type)
285+
286+
def test_from_number(self, cls=float):
287+
def eq(actual, expected):
288+
self.assertEqual(actual, expected)
289+
self.assertIs(type(actual), cls)
290+
291+
eq(cls.from_number(3.14), 3.14)
292+
eq(cls.from_number(314), 314.0)
293+
eq(cls.from_number(OtherFloatSubclass(3.14)), 3.14)
294+
eq(cls.from_number(FloatLike(3.14)), 3.14)
295+
eq(cls.from_number(MyIndex(314)), 314.0)
296+
297+
x = cls.from_number(NAN)
298+
self.assertTrue(x != x)
299+
self.assertIs(type(x), cls)
300+
if cls is float:
301+
self.assertIs(cls.from_number(NAN), NAN)
302+
303+
self.assertRaises(TypeError, cls.from_number, '3.14')
304+
self.assertRaises(TypeError, cls.from_number, b'3.14')
305+
self.assertRaises(TypeError, cls.from_number, 3.14j)
306+
self.assertRaises(TypeError, cls.from_number, MyInt(314))
307+
self.assertRaises(TypeError, cls.from_number, {})
308+
self.assertRaises(TypeError, cls.from_number)
309+
310+
def test_from_number_subclass(self):
311+
self.test_from_number(FloatSubclass)
312+
280313
def test_is_integer(self):
281314
self.assertFalse((1.1).is_integer())
282315
self.assertTrue((1.).is_integer())
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add class methods :meth:`float.from_number` and :meth:`complex.from_number`.

Objects/clinic/complexobject.c.h

+10-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Objects/clinic/floatobject.c.h

+10-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Objects/complexobject.c

+46-16
Original file line numberDiff line numberDiff line change
@@ -757,22 +757,6 @@ complex___complex___impl(PyComplexObject *self)
757757
}
758758

759759

760-
static PyMethodDef complex_methods[] = {
761-
COMPLEX_CONJUGATE_METHODDEF
762-
COMPLEX___COMPLEX___METHODDEF
763-
COMPLEX___GETNEWARGS___METHODDEF
764-
COMPLEX___FORMAT___METHODDEF
765-
{NULL, NULL} /* sentinel */
766-
};
767-
768-
static PyMemberDef complex_members[] = {
769-
{"real", Py_T_DOUBLE, offsetof(PyComplexObject, cval.real), Py_READONLY,
770-
"the real part of a complex number"},
771-
{"imag", Py_T_DOUBLE, offsetof(PyComplexObject, cval.imag), Py_READONLY,
772-
"the imaginary part of a complex number"},
773-
{0},
774-
};
775-
776760
static PyObject *
777761
complex_from_string_inner(const char *s, Py_ssize_t len, void *type)
778762
{
@@ -1142,6 +1126,52 @@ complex_new_impl(PyTypeObject *type, PyObject *r, PyObject *i)
11421126
return complex_subtype_from_doubles(type, cr.real, ci.real);
11431127
}
11441128

1129+
/*[clinic input]
1130+
@classmethod
1131+
complex.from_number
1132+
1133+
number: object
1134+
/
1135+
1136+
Convert number to a complex floating-point number.
1137+
[clinic start generated code]*/
1138+
1139+
static PyObject *
1140+
complex_from_number(PyTypeObject *type, PyObject *number)
1141+
/*[clinic end generated code: output=658a7a5fb0de074d input=3f8bdd3a2bc3facd]*/
1142+
{
1143+
if (PyComplex_CheckExact(number) && type == &PyComplex_Type) {
1144+
Py_INCREF(number);
1145+
return number;
1146+
}
1147+
Py_complex cv = PyComplex_AsCComplex(number);
1148+
if (cv.real == -1.0 && PyErr_Occurred()) {
1149+
return NULL;
1150+
}
1151+
PyObject *result = PyComplex_FromCComplex(cv);
1152+
if (type != &PyComplex_Type && result != NULL) {
1153+
Py_SETREF(result, PyObject_CallOneArg((PyObject *)type, result));
1154+
}
1155+
return result;
1156+
}
1157+
1158+
static PyMethodDef complex_methods[] = {
1159+
COMPLEX_FROM_NUMBER_METHODDEF
1160+
COMPLEX_CONJUGATE_METHODDEF
1161+
COMPLEX___COMPLEX___METHODDEF
1162+
COMPLEX___GETNEWARGS___METHODDEF
1163+
COMPLEX___FORMAT___METHODDEF
1164+
{NULL, NULL} /* sentinel */
1165+
};
1166+
1167+
static PyMemberDef complex_members[] = {
1168+
{"real", Py_T_DOUBLE, offsetof(PyComplexObject, cval.real), Py_READONLY,
1169+
"the real part of a complex number"},
1170+
{"imag", Py_T_DOUBLE, offsetof(PyComplexObject, cval.imag), Py_READONLY,
1171+
"the imaginary part of a complex number"},
1172+
{0},
1173+
};
1174+
11451175
static PyNumberMethods complex_as_number = {
11461176
(binaryfunc)complex_add, /* nb_add */
11471177
(binaryfunc)complex_sub, /* nb_subtract */

0 commit comments

Comments
 (0)