mirror of https://github.com/python/cpython
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.
This commit is contained in:
parent
8303d32ff5
commit
94bee45dee
|
@ -438,6 +438,8 @@ are always available. They are listed here in alphabetical order.
|
|||
If one of arguments is a real number, only its real component is used in
|
||||
the above expressions.
|
||||
|
||||
See also :meth:`complex.from_number` which only accepts a single numeric argument.
|
||||
|
||||
If all arguments are omitted, returns ``0j``.
|
||||
|
||||
The complex type is described in :ref:`typesnumeric`.
|
||||
|
@ -788,6 +790,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 accepts a numeric argument.
|
||||
|
||||
If no argument is given, ``0.0`` is returned.
|
||||
|
||||
The float type is described in :ref:`typesnumeric`.
|
||||
|
|
|
@ -625,6 +625,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*.
|
||||
|
||||
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.14
|
||||
|
||||
|
||||
.. method:: float.as_integer_ratio()
|
||||
|
||||
Return a pair of integers whose ratio is exactly equal to the
|
||||
|
@ -703,6 +720,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.14
|
||||
|
||||
|
||||
.. _numeric-hash:
|
||||
|
||||
Hashing of numeric types
|
||||
|
|
|
@ -75,6 +75,10 @@ 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 an error if the argument is a string.
|
||||
(Contributed by Serhiy Storchaka in :gh:`84978`.)
|
||||
|
||||
|
||||
New Modules
|
||||
|
|
|
@ -36,6 +36,16 @@ class WithFloat:
|
|||
class ComplexSubclass(complex):
|
||||
pass
|
||||
|
||||
class OtherComplexSubclass(complex):
|
||||
pass
|
||||
|
||||
class MyInt:
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def __int__(self):
|
||||
return self.value
|
||||
|
||||
class WithComplex:
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
@ -675,6 +685,35 @@ class ComplexTest(unittest.TestCase):
|
|||
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(WithComplex(3.14+2.72j)), 3.14+2.72j)
|
||||
eq(cls.from_number(WithFloat(3.14)), 3.14+0j)
|
||||
eq(cls.from_number(WithIndex(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)))
|
||||
|
|
|
@ -32,6 +32,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):
|
||||
|
@ -181,10 +203,6 @@ class GeneralFloatCases(unittest.TestCase):
|
|||
|
||||
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.
|
||||
|
@ -206,45 +224,29 @@ class GeneralFloatCases(unittest.TestCase):
|
|||
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'):
|
||||
|
@ -277,6 +279,37 @@ class GeneralFloatCases(unittest.TestCase):
|
|||
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())
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Add class methods :meth:`float.from_number` and :meth:`complex.from_number`.
|
|
@ -160,4 +160,13 @@ skip_optional_pos:
|
|||
exit:
|
||||
return return_value;
|
||||
}
|
||||
/*[clinic end generated code: output=295ecfd71389d7fe input=a9049054013a1b77]*/
|
||||
|
||||
PyDoc_STRVAR(complex_from_number__doc__,
|
||||
"from_number($type, number, /)\n"
|
||||
"--\n"
|
||||
"\n"
|
||||
"Convert number to a complex floating-point number.");
|
||||
|
||||
#define COMPLEX_FROM_NUMBER_METHODDEF \
|
||||
{"from_number", (PyCFunction)complex_from_number, METH_O|METH_CLASS, complex_from_number__doc__},
|
||||
/*[clinic end generated code: output=188438cc9ae167f7 input=a9049054013a1b77]*/
|
||||
|
|
|
@ -227,6 +227,15 @@ exit:
|
|||
return return_value;
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(float_from_number__doc__,
|
||||
"from_number($type, number, /)\n"
|
||||
"--\n"
|
||||
"\n"
|
||||
"Convert real number to a floating-point number.");
|
||||
|
||||
#define FLOAT_FROM_NUMBER_METHODDEF \
|
||||
{"from_number", (PyCFunction)float_from_number, METH_O|METH_CLASS, float_from_number__doc__},
|
||||
|
||||
PyDoc_STRVAR(float___getnewargs____doc__,
|
||||
"__getnewargs__($self, /)\n"
|
||||
"--\n"
|
||||
|
@ -318,4 +327,4 @@ float___format__(PyObject *self, PyObject *arg)
|
|||
exit:
|
||||
return return_value;
|
||||
}
|
||||
/*[clinic end generated code: output=c79743c8551c30d9 input=a9049054013a1b77]*/
|
||||
/*[clinic end generated code: output=b9c8a1b6759ca073 input=a9049054013a1b77]*/
|
||||
|
|
|
@ -757,22 +757,6 @@ complex___complex___impl(PyComplexObject *self)
|
|||
}
|
||||
|
||||
|
||||
static PyMethodDef complex_methods[] = {
|
||||
COMPLEX_CONJUGATE_METHODDEF
|
||||
COMPLEX___COMPLEX___METHODDEF
|
||||
COMPLEX___GETNEWARGS___METHODDEF
|
||||
COMPLEX___FORMAT___METHODDEF
|
||||
{NULL, NULL} /* sentinel */
|
||||
};
|
||||
|
||||
static PyMemberDef complex_members[] = {
|
||||
{"real", Py_T_DOUBLE, offsetof(PyComplexObject, cval.real), Py_READONLY,
|
||||
"the real part of a complex number"},
|
||||
{"imag", Py_T_DOUBLE, offsetof(PyComplexObject, cval.imag), Py_READONLY,
|
||||
"the imaginary part of a complex number"},
|
||||
{0},
|
||||
};
|
||||
|
||||
static PyObject *
|
||||
complex_from_string_inner(const char *s, Py_ssize_t len, void *type)
|
||||
{
|
||||
|
@ -1142,6 +1126,52 @@ complex_new_impl(PyTypeObject *type, PyObject *r, PyObject *i)
|
|||
return complex_subtype_from_doubles(type, cr.real, ci.real);
|
||||
}
|
||||
|
||||
/*[clinic input]
|
||||
@classmethod
|
||||
complex.from_number
|
||||
|
||||
number: object
|
||||
/
|
||||
|
||||
Convert number to a complex floating-point number.
|
||||
[clinic start generated code]*/
|
||||
|
||||
static PyObject *
|
||||
complex_from_number(PyTypeObject *type, PyObject *number)
|
||||
/*[clinic end generated code: output=658a7a5fb0de074d input=3f8bdd3a2bc3facd]*/
|
||||
{
|
||||
if (PyComplex_CheckExact(number) && type == &PyComplex_Type) {
|
||||
Py_INCREF(number);
|
||||
return number;
|
||||
}
|
||||
Py_complex cv = PyComplex_AsCComplex(number);
|
||||
if (cv.real == -1.0 && PyErr_Occurred()) {
|
||||
return NULL;
|
||||
}
|
||||
PyObject *result = PyComplex_FromCComplex(cv);
|
||||
if (type != &PyComplex_Type && result != NULL) {
|
||||
Py_SETREF(result, PyObject_CallOneArg((PyObject *)type, result));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static PyMethodDef complex_methods[] = {
|
||||
COMPLEX_FROM_NUMBER_METHODDEF
|
||||
COMPLEX_CONJUGATE_METHODDEF
|
||||
COMPLEX___COMPLEX___METHODDEF
|
||||
COMPLEX___GETNEWARGS___METHODDEF
|
||||
COMPLEX___FORMAT___METHODDEF
|
||||
{NULL, NULL} /* sentinel */
|
||||
};
|
||||
|
||||
static PyMemberDef complex_members[] = {
|
||||
{"real", Py_T_DOUBLE, offsetof(PyComplexObject, cval.real), Py_READONLY,
|
||||
"the real part of a complex number"},
|
||||
{"imag", Py_T_DOUBLE, offsetof(PyComplexObject, cval.imag), Py_READONLY,
|
||||
"the imaginary part of a complex number"},
|
||||
{0},
|
||||
};
|
||||
|
||||
static PyNumberMethods complex_as_number = {
|
||||
(binaryfunc)complex_add, /* nb_add */
|
||||
(binaryfunc)complex_sub, /* nb_subtract */
|
||||
|
|
|
@ -1669,6 +1669,36 @@ float_vectorcall(PyObject *type, PyObject * const*args,
|
|||
}
|
||||
|
||||
|
||||
/*[clinic input]
|
||||
@classmethod
|
||||
float.from_number
|
||||
|
||||
number: object
|
||||
/
|
||||
|
||||
Convert real number to a floating-point number.
|
||||
[clinic start generated code]*/
|
||||
|
||||
static PyObject *
|
||||
float_from_number(PyTypeObject *type, PyObject *number)
|
||||
/*[clinic end generated code: output=bbcf05529fe907a3 input=1f8424d9bc11866a]*/
|
||||
{
|
||||
if (PyFloat_CheckExact(number) && type == &PyFloat_Type) {
|
||||
Py_INCREF(number);
|
||||
return number;
|
||||
}
|
||||
double x = PyFloat_AsDouble(number);
|
||||
if (x == -1.0 && PyErr_Occurred()) {
|
||||
return NULL;
|
||||
}
|
||||
PyObject *result = PyFloat_FromDouble(x);
|
||||
if (type != &PyFloat_Type && result != NULL) {
|
||||
Py_SETREF(result, PyObject_CallOneArg((PyObject *)type, result));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/*[clinic input]
|
||||
float.__getnewargs__
|
||||
[clinic start generated code]*/
|
||||
|
@ -1782,6 +1812,7 @@ float___format___impl(PyObject *self, PyObject *format_spec)
|
|||
}
|
||||
|
||||
static PyMethodDef float_methods[] = {
|
||||
FLOAT_FROM_NUMBER_METHODDEF
|
||||
FLOAT_CONJUGATE_METHODDEF
|
||||
FLOAT___TRUNC___METHODDEF
|
||||
FLOAT___FLOOR___METHODDEF
|
||||
|
|
Loading…
Reference in New Issue