Skip to content

Commit 0e51833

Browse files
committed
gh-131704: add PyNumber_Complex() function
1 parent dc4e778 commit 0e51833

File tree

10 files changed

+126
-46
lines changed

10 files changed

+126
-46
lines changed

Doc/c-api/number.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,16 @@ Number Protocol
252252
This is the equivalent of the Python expression ``float(o)``.
253253
254254
255+
.. c:function:: PyObject* PyNumber_Complex(PyObject *o)
256+
257+
.. index:: pair: built-in function; complex
258+
259+
Returns the *o* converted to a complex object on success, or ``NULL`` on failure.
260+
This is the equivalent of the Python expression ``complex(o)``.
261+
262+
.. versionadded:: next
263+
264+
255265
.. c:function:: PyObject* PyNumber_Index(PyObject *o)
256266
257267
Returns the *o* converted to a Python int on success or ``NULL`` with a

Doc/data/stable_abi.dat

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/abstract.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,12 @@ PyAPI_FUNC(PyObject *) PyNumber_Long(PyObject *o);
564564
This is the equivalent of the Python expression: float(o). */
565565
PyAPI_FUNC(PyObject *) PyNumber_Float(PyObject *o);
566566

567+
/* Returns the object 'o' converted to a complex object on success, or NULL
568+
on failure.
569+
570+
This is the equivalent of the Python expression: complex(o). */
571+
PyAPI_FUNC(PyObject *) PyNumber_Complex(PyObject *o);
572+
567573

568574
/* --- In-place variants of (some of) the above number protocol functions -- */
569575

Lib/test/test_capi/test_number.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ class IntLike(WithDunder):
5757
class FloatLike(WithDunder):
5858
methname = '__float__'
5959

60+
class ComplexLike(WithDunder):
61+
methname = '__complex__'
62+
6063

6164
def subclassof(base):
6265
return type(base.__name__ + 'Subclass', (base,), {})
@@ -302,6 +305,47 @@ def test_float(self):
302305
self.assertRaises(TypeError, float_, object())
303306
self.assertRaises(SystemError, float_, NULL)
304307

308+
def test_complex(self):
309+
# Test PyNumber_Complex()
310+
complex_ = _testcapi.number_complex
311+
312+
self.assertEqual(complex_(1.25), 1.25+0j)
313+
self.assertEqual(complex_(123), 123+0j)
314+
self.assertEqual(complex_("1.25"), 1.25+0j)
315+
self.assertEqual(complex_(1+2j), 1+2j)
316+
self.assertEqual(complex_("1+2j"), 1+2j)
317+
318+
self.assertEqual(complex_(FloatLike.with_val(4.25)), 4.25 + 0j)
319+
self.assertEqual(complex_(IndexLike.with_val(99)), 99.0 + 0j)
320+
self.assertEqual(complex_(IndexLike.with_val(-1)), -1.0 + 0j)
321+
self.assertEqual(complex_(ComplexLike.with_val(1+2j)), 1+2j)
322+
323+
self.assertRaises(TypeError, complex_, FloatLike.with_val(687))
324+
x = FloatLike.with_val(subclassof(float)(4.25))
325+
with warnings.catch_warnings():
326+
warnings.simplefilter("error", DeprecationWarning)
327+
self.assertRaises(DeprecationWarning, complex_, x)
328+
with self.assertWarns(DeprecationWarning):
329+
self.assertEqual(complex_(x), 4.25+0j)
330+
self.assertRaises(RuntimeError, complex_,
331+
FloatLike.with_exc(RuntimeError))
332+
333+
self.assertRaises(TypeError, complex_, ComplexLike.with_val(687))
334+
x = ComplexLike.with_val(subclassof(complex)(1+2j))
335+
with warnings.catch_warnings():
336+
warnings.simplefilter("error", DeprecationWarning)
337+
self.assertRaises(DeprecationWarning, complex_, x)
338+
with self.assertWarns(DeprecationWarning):
339+
self.assertEqual(complex_(x), 1+2j)
340+
self.assertRaises(RuntimeError, complex_,
341+
ComplexLike.with_exc(RuntimeError))
342+
343+
self.assertRaises(TypeError, complex_, IndexLike.with_val(1.25))
344+
self.assertRaises(OverflowError, complex_, IndexLike.with_val(2**2000))
345+
346+
self.assertRaises(TypeError, complex_, object())
347+
self.assertRaises(SystemError, complex_, NULL)
348+
305349
def test_index(self):
306350
# Test PyNumber_Index()
307351
index = _testcapi.number_index

Lib/test/test_stable_abi_ctypes.py

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Misc/stable_abi.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2546,3 +2546,5 @@
25462546
added = '3.14'
25472547
[function.PyComplex_FromString]
25482548
added = '3.14'
2549+
[function.PyNumber_Complex]
2550+
added = '3.14'

Modules/_testcapi/numbers.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ BINARYFUNC(InPlaceOr, inplaceor)
8787

8888
UNARYFUNC(Long, long)
8989
UNARYFUNC(Float, float)
90+
UNARYFUNC(Complex, complex)
9091
UNARYFUNC(Index, index)
9192

9293
static PyObject *
@@ -160,6 +161,7 @@ static PyMethodDef test_methods[] = {
160161
{"number_inplaceor", number_inplaceor, METH_VARARGS},
161162
{"number_long", number_long, METH_O},
162163
{"number_float", number_float, METH_O},
164+
{"number_complex", number_complex, METH_O},
163165
{"number_index", number_index, METH_O},
164166
{"number_tobase", number_tobase, METH_VARARGS},
165167
{"number_asssizet", number_asssizet, METH_VARARGS},

Objects/abstract.c

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1648,6 +1648,56 @@ PyNumber_Float(PyObject *o)
16481648
return PyFloat_FromString(o);
16491649
}
16501650

1651+
PyObject *
1652+
PyNumber_Complex(PyObject *o)
1653+
{
1654+
if (o == NULL) {
1655+
return null_error();
1656+
}
1657+
1658+
if (PyComplex_CheckExact(o)) {
1659+
return Py_NewRef(o);
1660+
}
1661+
1662+
PyObject *f = _PyObject_LookupSpecial(o, &_Py_ID(__complex__));
1663+
if (f) {
1664+
PyObject *res = _PyObject_CallNoArgs(f);
1665+
Py_DECREF(f);
1666+
if (!res || PyComplex_CheckExact(res)) {
1667+
return res;
1668+
}
1669+
if (!PyComplex_Check(res)) {
1670+
PyErr_Format(PyExc_TypeError,
1671+
"%.50s.__complex__ returned non-complex (type %.50s)",
1672+
Py_TYPE(o)->tp_name, Py_TYPE(res)->tp_name);
1673+
Py_DECREF(res);
1674+
return NULL;
1675+
}
1676+
/* Issue #26983: warn if 'res' not of exact type complex. */
1677+
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
1678+
"%.50s.__complex__ returned non-complex (type %.50s). "
1679+
"The ability to return an instance of a strict subclass of complex "
1680+
"is deprecated, and may be removed in a future version of Python.",
1681+
Py_TYPE(o)->tp_name, Py_TYPE(res)->tp_name)) {
1682+
Py_DECREF(res);
1683+
return NULL;
1684+
}
1685+
Py_complex cv = ((PyComplexObject *)res)->cval;
1686+
Py_DECREF(res);
1687+
return PyComplex_FromCComplex(cv);
1688+
}
1689+
1690+
PyNumberMethods *m = Py_TYPE(o)->tp_as_number;
1691+
if (m && (m->nb_float || m->nb_index)) {
1692+
double real = PyFloat_AsDouble(o);
1693+
if (real != -1 || !PyErr_Occurred()) {
1694+
return PyComplex_FromDoubles(real, 0.0);
1695+
}
1696+
return NULL;
1697+
}
1698+
1699+
return PyComplex_FromString(o);
1700+
}
16511701

16521702
PyObject *
16531703
PyNumber_ToBase(PyObject *n, int base)

Objects/complexobject.c

Lines changed: 9 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,61 +1099,24 @@ PyComplex_FromString(PyObject *op)
10991099
static PyObject *
11001100
actual_complex_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
11011101
{
1102-
PyObject *res = NULL;
1103-
PyNumberMethods *nbr;
1102+
Py_ssize_t nargs = PyTuple_GET_SIZE(args);
11041103

1105-
if (PyTuple_GET_SIZE(args) > 1 || (kwargs != NULL && PyDict_GET_SIZE(kwargs))) {
1104+
if (nargs > 1 || (kwargs != NULL && PyDict_GET_SIZE(kwargs))) {
11061105
return complex_new(type, args, kwargs);
11071106
}
1108-
if (!PyTuple_GET_SIZE(args)) {
1107+
if (!nargs) {
11091108
return complex_subtype_from_doubles(type, 0, 0);
11101109
}
11111110

11121111
PyObject *arg = PyTuple_GET_ITEM(args, 0);
1113-
/* Special-case for a single argument when type(arg) is complex. */
1114-
if (PyComplex_CheckExact(arg) && type == &PyComplex_Type) {
1115-
/* Note that we can't know whether it's safe to return
1116-
a complex *subclass* instance as-is, hence the restriction
1117-
to exact complexes here. If either the input or the
1118-
output is a complex subclass, it will be handled below
1119-
as a non-orthogonal vector. */
1120-
return Py_NewRef(arg);
1121-
}
1122-
if (PyUnicode_Check(arg)) {
1123-
return complex_subtype_from_string(type, arg);
1124-
}
1125-
PyObject *tmp = try_complex_special_method(arg);
1126-
if (tmp) {
1127-
Py_complex c = ((PyComplexObject*)tmp)->cval;
1128-
res = complex_subtype_from_doubles(type, c.real, c.imag);
1129-
Py_DECREF(tmp);
1130-
}
1131-
else if (PyErr_Occurred()) {
1132-
return NULL;
1133-
}
1134-
else if (PyComplex_Check(arg)) {
1135-
/* Note that if arg is of a complex subtype, we're only
1136-
retaining its real & imag parts here, and the return
1137-
value is (properly) of the builtin complex type. */
1138-
Py_complex c = ((PyComplexObject*)arg)->cval;
1112+
PyObject *res = PyNumber_Complex(arg);
1113+
1114+
if (res && type != &PyComplex_Type) {
1115+
Py_complex c = _PyComplexObject_CAST(res)->cval;
1116+
1117+
Py_DECREF(res);
11391118
res = complex_subtype_from_doubles(type, c.real, c.imag);
11401119
}
1141-
else if ((nbr = Py_TYPE(arg)->tp_as_number) != NULL &&
1142-
(nbr->nb_float != NULL || nbr->nb_index != NULL))
1143-
{
1144-
/* The argument really is entirely real, and contributes
1145-
nothing in the imaginary direction.
1146-
Just treat it as a double. */
1147-
double r = PyFloat_AsDouble(arg);
1148-
if (r != -1.0 || !PyErr_Occurred()) {
1149-
res = complex_subtype_from_doubles(type, r, 0);
1150-
}
1151-
}
1152-
else {
1153-
PyErr_Format(PyExc_TypeError,
1154-
"complex() argument must be a string or a number, not %T",
1155-
arg);
1156-
}
11571120
return res;
11581121
}
11591122

PC/python3dll.c

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)