diff --git a/Doc/c-api/complex.rst b/Doc/c-api/complex.rst index d1f5d8eda676ef..baca9e0e009458 100644 --- a/Doc/c-api/complex.rst +++ b/Doc/c-api/complex.rst @@ -207,6 +207,13 @@ Complex Numbers as Python Objects .. versionchanged:: 3.13 Use :meth:`~object.__complex__` if available. +.. c:function:: PyObject* PyComplex_FromString(PyObject *str) + + Create a Python complex number object from the string value in *str* or + return ``NULL`` with an exception set on error. + + .. versionadded:: next + .. c:function:: Py_complex PyComplex_AsCComplex(PyObject *op) Return the :c:type:`Py_complex` value of the complex number *op*. diff --git a/Doc/c-api/number.rst b/Doc/c-api/number.rst index ad8b5935258fa7..2b4e5867aee204 100644 --- a/Doc/c-api/number.rst +++ b/Doc/c-api/number.rst @@ -252,6 +252,16 @@ Number Protocol This is the equivalent of the Python expression ``float(o)``. +.. c:function:: PyObject* PyNumber_Complex(PyObject *o) + + .. index:: pair: built-in function; complex + + Returns the *o* converted to a complex object on success, or ``NULL`` on failure. + This is the equivalent of the Python expression ``complex(o)``. + + .. versionadded:: next + + .. c:function:: PyObject* PyNumber_Index(PyObject *o) Returns the *o* converted to a Python int on success or ``NULL`` with a diff --git a/Doc/c-api/typeobj.rst b/Doc/c-api/typeobj.rst index 9d551aa997ba1f..ad017107981d93 100644 --- a/Doc/c-api/typeobj.rst +++ b/Doc/c-api/typeobj.rst @@ -276,7 +276,7 @@ sub-slots +---------------------------------------------------------+-----------------------------------+---------------+ | :c:member:`~PyNumberMethods.nb_int` | :c:type:`unaryfunc` | __int__ | +---------------------------------------------------------+-----------------------------------+---------------+ - | :c:member:`~PyNumberMethods.nb_reserved` | void * | | + | :c:member:`~PyNumberMethods.nb_complex` | :c:type:`unaryfunc` | __complex__ | +---------------------------------------------------------+-----------------------------------+---------------+ | :c:member:`~PyNumberMethods.nb_float` | :c:type:`unaryfunc` | __float__ | +---------------------------------------------------------+-----------------------------------+---------------+ @@ -2304,7 +2304,7 @@ Number Object Structures binaryfunc nb_xor; binaryfunc nb_or; unaryfunc nb_int; - void *nb_reserved; + unaryfunc nb_complex; unaryfunc nb_float; binaryfunc nb_inplace_add; @@ -2338,11 +2338,6 @@ Number Object Structures ``Py_NotImplemented``, if another error occurred they must return ``NULL`` and set an exception. - .. note:: - - The :c:member:`~PyNumberMethods.nb_reserved` field should always be ``NULL``. It - was previously called :c:member:`!nb_long`, and was renamed in - Python 3.0.1. .. c:member:: binaryfunc PyNumberMethods.nb_add .. c:member:: binaryfunc PyNumberMethods.nb_subtract @@ -2361,7 +2356,7 @@ Number Object Structures .. c:member:: binaryfunc PyNumberMethods.nb_xor .. c:member:: binaryfunc PyNumberMethods.nb_or .. c:member:: unaryfunc PyNumberMethods.nb_int -.. c:member:: void *PyNumberMethods.nb_reserved +.. c:member:: unaryfunc PyNumberMethods.nb_complex .. c:member:: unaryfunc PyNumberMethods.nb_float .. c:member:: binaryfunc PyNumberMethods.nb_inplace_add .. c:member:: binaryfunc PyNumberMethods.nb_inplace_subtract diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index c15f82603aa944..b489b4ac684597 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -89,6 +89,7 @@ func,PyCodec_StrictErrors,3.2,, func,PyCodec_Unregister,3.10,, func,PyCodec_XMLCharRefReplaceErrors,3.2,, func,PyComplex_FromDoubles,3.2,, +func,PyComplex_FromString,3.14,, func,PyComplex_ImagAsDouble,3.2,, func,PyComplex_RealAsDouble,3.2,, data,PyComplex_Type,3.2,, @@ -450,6 +451,7 @@ func,PyNumber_Add,3.2,, func,PyNumber_And,3.2,, func,PyNumber_AsSsize_t,3.2,, func,PyNumber_Check,3.2,, +func,PyNumber_Complex,3.14,, func,PyNumber_Divmod,3.2,, func,PyNumber_Float,3.2,, func,PyNumber_FloorDivide,3.2,, diff --git a/Include/abstract.h b/Include/abstract.h index b9199fc03a399a..6d5f1e9d83655b 100644 --- a/Include/abstract.h +++ b/Include/abstract.h @@ -564,6 +564,12 @@ PyAPI_FUNC(PyObject *) PyNumber_Long(PyObject *o); This is the equivalent of the Python expression: float(o). */ PyAPI_FUNC(PyObject *) PyNumber_Float(PyObject *o); +/* Returns the object 'o' converted to a complex object on success, or NULL + on failure. + + This is the equivalent of the Python expression: complex(o). */ +PyAPI_FUNC(PyObject *) PyNumber_Complex(PyObject *o); + /* --- In-place variants of (some of) the above number protocol functions -- */ diff --git a/Include/complexobject.h b/Include/complexobject.h index ebe49a832f7414..c75c656bd7142e 100644 --- a/Include/complexobject.h +++ b/Include/complexobject.h @@ -14,6 +14,7 @@ PyAPI_DATA(PyTypeObject) PyComplex_Type; #define PyComplex_CheckExact(op) Py_IS_TYPE((op), &PyComplex_Type) PyAPI_FUNC(PyObject *) PyComplex_FromDoubles(double real, double imag); +PyAPI_FUNC(PyObject *) PyComplex_FromString(PyObject *op); PyAPI_FUNC(double) PyComplex_RealAsDouble(PyObject *op); PyAPI_FUNC(double) PyComplex_ImagAsDouble(PyObject *op); diff --git a/Include/cpython/object.h b/Include/cpython/object.h index e2300aee7a207a..2e32ce6f03c997 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -80,7 +80,7 @@ typedef struct { binaryfunc nb_xor; binaryfunc nb_or; unaryfunc nb_int; - void *nb_reserved; /* the slot formerly known as nb_long */ + unaryfunc nb_complex; /* the slot formerly known as nb_long */ unaryfunc nb_float; binaryfunc nb_inplace_add; diff --git a/Include/typeslots.h b/Include/typeslots.h index a7f3017ec02e92..063ba2426dfaf5 100644 --- a/Include/typeslots.h +++ b/Include/typeslots.h @@ -94,3 +94,7 @@ /* New in 3.14 */ #define Py_tp_token 83 #endif +#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030E0000 +/* New in 3.14 */ +#define Py_nb_complex 84 +#endif diff --git a/Lib/test/test_capi/test_complex.py b/Lib/test/test_capi/test_complex.py index 97e0eb3f043080..294d88a08401d8 100644 --- a/Lib/test/test_capi/test_complex.py +++ b/Lib/test/test_capi/test_complex.py @@ -131,6 +131,19 @@ def test_imagasdouble(self): # CRASHES imagasdouble(NULL) + def test_fromstring(self): + # Test PyComplex_FromString() + fromstring = _testlimitedcapi.complex_fromstring + + self.assertEqual(fromstring("1+2j"), 1+2j) + self.assertEqual(fromstring("(1+2j)"), 1+2j) + self.assertEqual(fromstring("2j"), 2j) + + self.assertRaises(ValueError, fromstring, "2j\0") + self.assertRaises(TypeError, fromstring, 2j) + + # CRASHES fromstring(NULL) + def test_asccomplex(self): # Test PyComplex_AsCComplex() asccomplex = _testcapi.complex_asccomplex @@ -293,6 +306,16 @@ def test_py_c_abs(self): self.assertEqual(_py_c_abs(complex(*[DBL_MAX]*2))[1], errno.ERANGE) + def test_old__complex__(self): + old_complex_like = _testcapi.old_complex_like + + x = old_complex_like() + with self.assertWarns(DeprecationWarning): + self.assertEqual(complex(x), 1+2j) + with warnings.catch_warnings(): + warnings.simplefilter("error", DeprecationWarning) + self.assertRaises(DeprecationWarning, complex, x) + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_capi/test_number.py b/Lib/test/test_capi/test_number.py index 8e7070307d670f..6de7c98318f8d9 100644 --- a/Lib/test/test_capi/test_number.py +++ b/Lib/test/test_capi/test_number.py @@ -57,6 +57,9 @@ class IntLike(WithDunder): class FloatLike(WithDunder): methname = '__float__' +class ComplexLike(WithDunder): + methname = '__complex__' + def subclassof(base): return type(base.__name__ + 'Subclass', (base,), {}) @@ -80,6 +83,7 @@ def test_check(self): self.assertTrue(check(0.5)) self.assertTrue(check(FloatLike.with_val(4.25))) self.assertTrue(check(1+2j)) + self.assertTrue(check(ComplexLike.with_val(1+2j))) self.assertFalse(check([])) self.assertFalse(check("abc")) @@ -302,6 +306,47 @@ def test_float(self): self.assertRaises(TypeError, float_, object()) self.assertRaises(SystemError, float_, NULL) + def test_complex(self): + # Test PyNumber_Complex() + complex_ = _testcapi.number_complex + + self.assertEqual(complex_(1.25), 1.25+0j) + self.assertEqual(complex_(123), 123+0j) + self.assertEqual(complex_("1.25"), 1.25+0j) + self.assertEqual(complex_(1+2j), 1+2j) + self.assertEqual(complex_("1+2j"), 1+2j) + + self.assertEqual(complex_(FloatLike.with_val(4.25)), 4.25 + 0j) + self.assertEqual(complex_(IndexLike.with_val(99)), 99.0 + 0j) + self.assertEqual(complex_(IndexLike.with_val(-1)), -1.0 + 0j) + self.assertEqual(complex_(ComplexLike.with_val(1+2j)), 1+2j) + + self.assertRaises(TypeError, complex_, FloatLike.with_val(687)) + x = FloatLike.with_val(subclassof(float)(4.25)) + with warnings.catch_warnings(): + warnings.simplefilter("error", DeprecationWarning) + self.assertRaises(DeprecationWarning, complex_, x) + with self.assertWarns(DeprecationWarning): + self.assertEqual(complex_(x), 4.25+0j) + self.assertRaises(RuntimeError, complex_, + FloatLike.with_exc(RuntimeError)) + + self.assertRaises(TypeError, complex_, ComplexLike.with_val(687)) + x = ComplexLike.with_val(subclassof(complex)(1+2j)) + with warnings.catch_warnings(): + warnings.simplefilter("error", DeprecationWarning) + self.assertRaises(DeprecationWarning, complex_, x) + with self.assertWarns(DeprecationWarning): + self.assertEqual(complex_(x), 1+2j) + self.assertRaises(RuntimeError, complex_, + ComplexLike.with_exc(RuntimeError)) + + self.assertRaises(TypeError, complex_, IndexLike.with_val(1.25)) + self.assertRaises(OverflowError, complex_, IndexLike.with_val(2**2000)) + + self.assertRaises(TypeError, complex_, object()) + self.assertRaises(SystemError, complex_, NULL) + def test_index(self): # Test PyNumber_Index() index = _testcapi.number_index diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index f3724ce6d4d15a..726f97619607ff 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -130,6 +130,7 @@ def test_windows_feature_macros(self): "PyCodec_Unregister", "PyCodec_XMLCharRefReplaceErrors", "PyComplex_FromDoubles", + "PyComplex_FromString", "PyComplex_ImagAsDouble", "PyComplex_RealAsDouble", "PyComplex_Type", @@ -483,6 +484,7 @@ def test_windows_feature_macros(self): "PyNumber_And", "PyNumber_AsSsize_t", "PyNumber_Check", + "PyNumber_Complex", "PyNumber_Divmod", "PyNumber_Float", "PyNumber_FloorDivide", diff --git a/Misc/NEWS.d/next/C_API/2025-03-25-12-53-40.gh-issue-131704.Q6amy8.rst b/Misc/NEWS.d/next/C_API/2025-03-25-12-53-40.gh-issue-131704.Q6amy8.rst new file mode 100644 index 00000000000000..ab597a557ce28e --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2025-03-25-12-53-40.gh-issue-131704.Q6amy8.rst @@ -0,0 +1,3 @@ +Add :c:func:`PyComplex_FromString` to create :class:`complex` numbers from +strings and :c:func:`PyNumber_Complex` (``complex(o)`` equivalent). Patch by +Sergey B Kirpichev. diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index 276526a1b6908e..bab27c43800f90 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -238,6 +238,8 @@ added = '3.2' [const.Py_nb_float] added = '3.2' +[const.Py_nb_complex] + added = '3.14' [const.Py_nb_inplace_add] added = '3.2' [const.Py_nb_inplace_subtract] @@ -2544,3 +2546,7 @@ added = '3.14' [function.Py_PACK_VERSION] added = '3.14' +[function.PyComplex_FromString] + added = '3.14' +[function.PyNumber_Complex] + added = '3.14' diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 9bba0e3354b26b..8056714bdb329b 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -3057,7 +3057,7 @@ static PyNumberMethods delta_as_number = { 0, /*nb_xor*/ 0, /*nb_or*/ 0, /*nb_int*/ - 0, /*nb_reserved*/ + 0, /*nb_complex*/ 0, /*nb_float*/ 0, /*nb_inplace_add*/ 0, /*nb_inplace_subtract*/ diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index c7a0eed1118434..871a93765a7ec8 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -4841,7 +4841,7 @@ dec_ceil(PyObject *self, PyObject *Py_UNUSED(dummy)) /* __complex__ */ static PyObject * -dec_complex(PyObject *self, PyObject *Py_UNUSED(dummy)) +dec_complex(PyObject *self) { PyObject *f; double x; @@ -5161,7 +5161,6 @@ static PyMethodDef dec_methods [] = { "__ceil__", dec_ceil, METH_NOARGS, NULL }, { "__floor__", dec_floor, METH_NOARGS, NULL }, { "__trunc__", dec_trunc, METH_NOARGS, NULL }, - { "__complex__", dec_complex, METH_NOARGS, NULL }, { "__sizeof__", dec_sizeof, METH_NOARGS, NULL }, { NULL, NULL, 1 } @@ -5194,6 +5193,7 @@ static PyType_Slot dec_slots[] = { {Py_nb_bool, nm_nonzero}, {Py_nb_int, nm_dec_as_long}, {Py_nb_float, PyDec_AsFloat}, + {Py_nb_complex, dec_complex}, {Py_nb_floor_divide, nm_mpd_qdivint}, {Py_nb_true_divide, nm_mpd_qdiv}, {0, NULL}, diff --git a/Modules/_testcapi/numbers.c b/Modules/_testcapi/numbers.c index e16ff73744067a..71a1a15911029f 100644 --- a/Modules/_testcapi/numbers.c +++ b/Modules/_testcapi/numbers.c @@ -87,6 +87,7 @@ BINARYFUNC(InPlaceOr, inplaceor) UNARYFUNC(Long, long) UNARYFUNC(Float, float) +UNARYFUNC(Complex, complex) UNARYFUNC(Index, index) static PyObject * @@ -160,6 +161,7 @@ static PyMethodDef test_methods[] = { {"number_inplaceor", number_inplaceor, METH_VARARGS}, {"number_long", number_long, METH_O}, {"number_float", number_float, METH_O}, + {"number_complex", number_complex, METH_O}, {"number_index", number_index, METH_O}, {"number_tobase", number_tobase, METH_VARARGS}, {"number_asssizet", number_asssizet, METH_VARARGS}, diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 3aa6e4c9e43a26..be9cd887970fea 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2684,7 +2684,7 @@ static PyNumberMethods matmulType_as_number = { 0, /* nb_xor */ 0, /* nb_or */ 0, /* nb_int */ - 0, /* nb_reserved */ + 0, /* nb_complex */ 0, /* nb_float */ 0, /* nb_inplace_add */ 0, /* nb_inplace_subtract */ @@ -3175,6 +3175,28 @@ create_manual_heap_type(void) return (PyObject *)type; } +/* Complex-like type with a __complex__ method, instead of nb_complex slot, + to test deprecation. */ + +static PyObject * +complex_dunder(PyObject *self, PyObject *Py_UNUSED(dummy)) +{ + return PyComplex_FromDoubles(1, 2); +} + +static PyMethodDef old_complex_methods[] = { + {"__complex__", complex_dunder, METH_NOARGS}, + {NULL, NULL} /* sentinel */ +}; + +PyTypeObject OldComplexLikeType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "old_complex_like", + .tp_new = PyType_GenericNew, + .tp_methods = old_complex_methods, +}; + + static struct PyModuleDef _testcapimodule = { PyModuleDef_HEAD_INIT, .m_name = "_testcapi", @@ -3315,6 +3337,10 @@ PyInit__testcapi(void) return NULL; } + if (PyType_Ready(&OldComplexLikeType) < 0) + return NULL; + Py_INCREF(&OldComplexLikeType); + PyModule_AddObject(m, "old_complex_like", (PyObject *)&OldComplexLikeType); /* Include tests from the _testcapi/ directory */ if (_PyTestCapi_Init_Vectorcall(m) < 0) { diff --git a/Modules/_testlimitedcapi/complex.c b/Modules/_testlimitedcapi/complex.c index e4c244e5c88d06..4f920a619c300d 100644 --- a/Modules/_testlimitedcapi/complex.c +++ b/Modules/_testlimitedcapi/complex.c @@ -58,6 +58,13 @@ complex_imagasdouble(PyObject *Py_UNUSED(module), PyObject *obj) return PyFloat_FromDouble(imag); } +static PyObject * +complex_fromstring(PyObject *Py_UNUSED(module), PyObject *obj) +{ + NULLABLE(obj); + return PyComplex_FromString(obj); +} + static PyMethodDef test_methods[] = { {"complex_check", complex_check, METH_O}, @@ -65,6 +72,7 @@ static PyMethodDef test_methods[] = { {"complex_fromdoubles", complex_fromdoubles, METH_VARARGS}, {"complex_realasdouble", complex_realasdouble, METH_O}, {"complex_imagasdouble", complex_imagasdouble, METH_O}, + {"complex_fromstring", complex_fromstring, METH_O}, {NULL}, }; diff --git a/Objects/abstract.c b/Objects/abstract.c index df96b935eccb44..d3a263927dc903 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -905,7 +905,7 @@ PyNumber_Check(PyObject *o) if (o == NULL) return 0; PyNumberMethods *nb = Py_TYPE(o)->tp_as_number; - return nb && (nb->nb_index || nb->nb_int || nb->nb_float || PyComplex_Check(o)); + return nb && (nb->nb_index || nb->nb_int || nb->nb_float || nb->nb_complex); } /* Binary operators */ @@ -1648,6 +1648,73 @@ PyNumber_Float(PyObject *o) return PyFloat_FromString(o); } +PyObject * +PyNumber_Complex(PyObject *o) +{ + if (o == NULL) { + return null_error(); + } + + if (PyComplex_CheckExact(o)) { + return Py_NewRef(o); + } + + PyNumberMethods *m = Py_TYPE(o)->tp_as_number; + PyObject *res = NULL; + + if (m && m->nb_complex) { + res = m->nb_complex(o); + goto complex_slot; + } + else { + PyObject *f = _PyObject_LookupSpecial(o, &_Py_ID(__complex__)); + if (f) { + res = _PyObject_CallNoArgs(f); + Py_DECREF(f); + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Use nb_complex slot to implement __complex__", 1)) { + Py_XDECREF(res); + return NULL; + } + goto complex_slot; + } + } + m = Py_TYPE(o)->tp_as_number; + if (m && (m->nb_float || m->nb_index)) { + double real = PyFloat_AsDouble(o); + if (real != -1 || !PyErr_Occurred()) { + return PyComplex_FromDoubles(real, 0.0); + } + return NULL; + } + return PyComplex_FromString(o); +complex_slot: + if (!res) { + return NULL; + } + if (PyComplex_CheckExact(res)) { + return res; + } + if (!PyComplex_Check(res)) { + PyErr_Format(PyExc_TypeError, + "%.50s.__complex__ returned non-complex (type %.50s)", + Py_TYPE(o)->tp_name, Py_TYPE(res)->tp_name); + Py_DECREF(res); + return NULL; + } + /* Issue #26983: warn if 'res' not of exact type complex. */ + if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1, + "%.50s.__complex__ returned non-complex (type %.50s). " + "The ability to return an instance of a strict subclass of complex " + "is deprecated, and may be removed in a future version of Python.", + Py_TYPE(o)->tp_name, Py_TYPE(res)->tp_name)) { + Py_DECREF(res); + return NULL; + } + Py_complex cv = ((PyComplexObject *)res)->cval; + Py_DECREF(res); + return PyComplex_FromCComplex(cv); +} PyObject * PyNumber_ToBase(PyObject *n, int base) diff --git a/Objects/boolobject.c b/Objects/boolobject.c index b694691ae4d0d1..11454091a78cd2 100644 --- a/Objects/boolobject.c +++ b/Objects/boolobject.c @@ -137,7 +137,7 @@ static PyNumberMethods bool_as_number = { bool_xor, /* nb_xor */ bool_or, /* nb_or */ 0, /* nb_int */ - 0, /* nb_reserved */ + 0, /* nb_complex */ 0, /* nb_float */ 0, /* nb_inplace_add */ 0, /* nb_inplace_subtract */ diff --git a/Objects/clinic/complexobject.c.h b/Objects/clinic/complexobject.c.h index c7303380de55f4..8615fd57a6d0dc 100644 --- a/Objects/clinic/complexobject.c.h +++ b/Objects/clinic/complexobject.c.h @@ -72,24 +72,6 @@ complex___format__(PyObject *self, PyObject *arg) return return_value; } -PyDoc_STRVAR(complex___complex____doc__, -"__complex__($self, /)\n" -"--\n" -"\n" -"Convert this value to exact type complex."); - -#define COMPLEX___COMPLEX___METHODDEF \ - {"__complex__", (PyCFunction)complex___complex__, METH_NOARGS, complex___complex____doc__}, - -static PyObject * -complex___complex___impl(PyComplexObject *self); - -static PyObject * -complex___complex__(PyObject *self, PyObject *Py_UNUSED(ignored)) -{ - return complex___complex___impl((PyComplexObject *)self); -} - PyDoc_STRVAR(complex_new__doc__, "complex(real=0, imag=0)\n" "--\n" @@ -185,4 +167,4 @@ complex_from_number(PyObject *type, PyObject *number) return return_value; } -/*[clinic end generated code: output=05d2ff43fc409733 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=ade9206f2f398f1c input=a9049054013a1b77]*/ diff --git a/Objects/complexobject.c b/Objects/complexobject.c index c2dd320ae73988..6c08993ce9f23b 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -488,34 +488,44 @@ PyComplex_ImagAsDouble(PyObject *op) static PyObject * try_complex_special_method(PyObject *op) { - PyObject *f; - - f = _PyObject_LookupSpecial(op, &_Py_ID(__complex__)); - if (f) { - PyObject *res = _PyObject_CallNoArgs(f); - Py_DECREF(f); - if (!res || PyComplex_CheckExact(res)) { - return res; - } - if (!PyComplex_Check(res)) { - PyErr_Format(PyExc_TypeError, - "__complex__ returned non-complex (type %.200s)", - Py_TYPE(res)->tp_name); - Py_DECREF(res); - return NULL; - } - /* Issue #29894: warn if 'res' not of exact type complex. */ - if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1, - "__complex__ returned non-complex (type %.200s). " - "The ability to return an instance of a strict subclass of complex " - "is deprecated, and may be removed in a future version of Python.", - Py_TYPE(res)->tp_name)) { - Py_DECREF(res); - return NULL; + PyNumberMethods *m = Py_TYPE(op)->tp_as_number; + PyObject *res = NULL; + + if (m && m->nb_complex) { + res = m->nb_complex(op); + } + else { + PyObject *f = _PyObject_LookupSpecial(op, &_Py_ID(__complex__)); + if (f) { + res = _PyObject_CallNoArgs(f); + Py_DECREF(f); + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Use nb_complex slot to implement __complex__", 1)) { + Py_XDECREF(res); + return NULL; + } } + } + if (!res || PyComplex_CheckExact(res)) { return res; } - return NULL; + if (!PyComplex_Check(res)) { + PyErr_Format(PyExc_TypeError, + "__complex__ returned non-complex (type %.200s)", + Py_TYPE(res)->tp_name); + Py_DECREF(res); + return NULL; + } + /* Issue #29894: warn if 'res' not of exact type complex. */ + if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1, + "__complex__ returned non-complex (type %.200s). " + "The ability to return an instance of a strict subclass of complex " + "is deprecated, and may be removed in a future version of Python.", + Py_TYPE(res)->tp_name)) { + Py_DECREF(res); + return NULL; + } + return res; } Py_complex @@ -908,25 +918,17 @@ complex___format___impl(PyComplexObject *self, PyObject *format_spec) return _PyUnicodeWriter_Finish(&writer); } -/*[clinic input] -complex.__complex__ - -Convert this value to exact type complex. -[clinic start generated code]*/ - static PyObject * -complex___complex___impl(PyComplexObject *self) -/*[clinic end generated code: output=e6b35ba3d275dc9c input=3589ada9d27db854]*/ +complex_complex(PyObject *self) { if (PyComplex_CheckExact(self)) { return Py_NewRef(self); } else { - return PyComplex_FromCComplex(self->cval); + return PyComplex_FromCComplex(((PyComplexObject *)self)->cval); } } - static PyObject * complex_from_string_inner(const char *s, Py_ssize_t len, void *type) { @@ -1080,6 +1082,12 @@ complex_subtype_from_string(PyTypeObject *type, PyObject *v) return result; } +PyObject * +PyComplex_FromString(PyObject *op) +{ + return complex_subtype_from_string(&PyComplex_Type, op); +} + /* The constructor should only accept a string as a positional argument, * not as by the 'real' keyword. But Argument Clinic does not allow * to distinguish between argument passed positionally and by keyword. @@ -1093,61 +1101,24 @@ complex_subtype_from_string(PyTypeObject *type, PyObject *v) static PyObject * actual_complex_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) { - PyObject *res = NULL; - PyNumberMethods *nbr; + Py_ssize_t nargs = PyTuple_GET_SIZE(args); - if (PyTuple_GET_SIZE(args) > 1 || (kwargs != NULL && PyDict_GET_SIZE(kwargs))) { + if (nargs > 1 || (kwargs != NULL && PyDict_GET_SIZE(kwargs))) { return complex_new(type, args, kwargs); } - if (!PyTuple_GET_SIZE(args)) { + if (!nargs) { return complex_subtype_from_doubles(type, 0, 0); } PyObject *arg = PyTuple_GET_ITEM(args, 0); - /* Special-case for a single argument when type(arg) is complex. */ - if (PyComplex_CheckExact(arg) && type == &PyComplex_Type) { - /* Note that we can't know whether it's safe to return - a complex *subclass* instance as-is, hence the restriction - to exact complexes here. If either the input or the - output is a complex subclass, it will be handled below - as a non-orthogonal vector. */ - return Py_NewRef(arg); - } - if (PyUnicode_Check(arg)) { - return complex_subtype_from_string(type, arg); - } - PyObject *tmp = try_complex_special_method(arg); - if (tmp) { - Py_complex c = ((PyComplexObject*)tmp)->cval; - res = complex_subtype_from_doubles(type, c.real, c.imag); - Py_DECREF(tmp); - } - else if (PyErr_Occurred()) { - return NULL; - } - else if (PyComplex_Check(arg)) { - /* Note that if arg is of a complex subtype, we're only - retaining its real & imag parts here, and the return - value is (properly) of the builtin complex type. */ - Py_complex c = ((PyComplexObject*)arg)->cval; + PyObject *res = PyNumber_Complex(arg); + + if (res && type != &PyComplex_Type) { + Py_complex c = _PyComplexObject_CAST(res)->cval; + + Py_DECREF(res); res = complex_subtype_from_doubles(type, c.real, c.imag); } - else if ((nbr = Py_TYPE(arg)->tp_as_number) != NULL && - (nbr->nb_float != NULL || nbr->nb_index != NULL)) - { - /* The argument really is entirely real, and contributes - nothing in the imaginary direction. - Just treat it as a double. */ - double r = PyFloat_AsDouble(arg); - if (r != -1.0 || !PyErr_Occurred()) { - res = complex_subtype_from_doubles(type, r, 0); - } - } - else { - PyErr_Format(PyExc_TypeError, - "complex() argument must be a string or a number, not %T", - arg); - } return res; } @@ -1328,7 +1299,6 @@ complex_from_number_impl(PyTypeObject *type, PyObject *number) static PyMethodDef complex_methods[] = { COMPLEX_FROM_NUMBER_METHODDEF COMPLEX_CONJUGATE_METHODDEF - COMPLEX___COMPLEX___METHODDEF COMPLEX___GETNEWARGS___METHODDEF COMPLEX___FORMAT___METHODDEF {NULL, NULL} /* sentinel */ @@ -1360,7 +1330,7 @@ static PyNumberMethods complex_as_number = { 0, /* nb_xor */ 0, /* nb_or */ 0, /* nb_int */ - 0, /* nb_reserved */ + complex_complex, /* nb_complex */ 0, /* nb_float */ 0, /* nb_inplace_add */ 0, /* nb_inplace_subtract */ diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 87a00bf1a458ea..21e67938a64224 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -1836,7 +1836,7 @@ static PyNumberMethods float_as_number = { 0, /* nb_xor */ 0, /* nb_or */ float___trunc___impl, /* nb_int */ - 0, /* nb_reserved */ + 0, /* nb_complex */ float_float, /* nb_float */ 0, /* nb_inplace_add */ 0, /* nb_inplace_subtract */ diff --git a/Objects/longobject.c b/Objects/longobject.c index 692312c1ad976c..b97908802e1f48 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -6592,7 +6592,7 @@ static PyNumberMethods long_as_number = { long_xor, /*nb_xor*/ long_or, /*nb_or*/ long_long, /*nb_int*/ - 0, /*nb_reserved*/ + 0, /*nb_complex*/ long_float, /*nb_float*/ 0, /* nb_inplace_add */ 0, /* nb_inplace_subtract */ diff --git a/Objects/object.c b/Objects/object.c index 99bb1d9c0bfad5..a882a4b58721cd 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2118,7 +2118,7 @@ static PyNumberMethods none_as_number = { 0, /* nb_xor */ 0, /* nb_or */ 0, /* nb_int */ - 0, /* nb_reserved */ + 0, /* nb_complex */ 0, /* nb_float */ 0, /* nb_inplace_add */ 0, /* nb_inplace_subtract */ diff --git a/Objects/setobject.c b/Objects/setobject.c index acbb53aafc0a26..db5f73127f1025 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -2484,7 +2484,7 @@ static PyNumberMethods set_as_number = { set_xor, /*nb_xor*/ set_or, /*nb_or*/ 0, /*nb_int*/ - 0, /*nb_reserved*/ + 0, /*nb_complex*/ 0, /*nb_float*/ 0, /*nb_inplace_add*/ set_isub, /*nb_inplace_subtract*/ diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 75c23ddd91b1a1..4ac8a9b1b0bd75 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -10047,6 +10047,7 @@ SLOT1BIN(slot_nb_xor, nb_xor, __xor__, __rxor__) SLOT1BIN(slot_nb_or, nb_or, __or__, __ror__) SLOT0(slot_nb_int, __int__) +SLOT0(slot_nb_complex, __complex__) SLOT0(slot_nb_float, __float__) SLOT1(slot_nb_inplace_add, __iadd__, PyObject *) SLOT1(slot_nb_inplace_subtract, __isub__, PyObject *) @@ -10862,6 +10863,8 @@ static pytype_slotdef slotdefs[] = { RBINSLOT(__ror__, nb_or, slot_nb_or, "|"), UNSLOT(__int__, nb_int, slot_nb_int, wrap_unaryfunc, "int(self)"), + UNSLOT(__complex__, nb_complex, slot_nb_complex, wrap_unaryfunc, + "complex(self)"), UNSLOT(__float__, nb_float, slot_nb_float, wrap_unaryfunc, "float(self)"), IBSLOT(__iadd__, nb_inplace_add, slot_nb_inplace_add, diff --git a/Objects/typeslots.inc b/Objects/typeslots.inc index 642160fe0bd8bc..877189ae4c1889 100644 --- a/Objects/typeslots.inc +++ b/Objects/typeslots.inc @@ -82,3 +82,4 @@ {offsetof(PyAsyncMethods, am_send), offsetof(PyTypeObject, tp_as_async)}, {-1, offsetof(PyTypeObject, tp_vectorcall)}, {-1, offsetof(PyHeapTypeObject, ht_token)}, +{offsetof(PyNumberMethods, nb_complex), offsetof(PyTypeObject, tp_as_number)}, diff --git a/Objects/weakrefobject.c b/Objects/weakrefobject.c index bd4c4ac9b3475a..7afbdea374a33b 100644 --- a/Objects/weakrefobject.c +++ b/Objects/weakrefobject.c @@ -809,7 +809,7 @@ static PyNumberMethods proxy_as_number = { proxy_xor, /*nb_xor*/ proxy_or, /*nb_or*/ proxy_int, /*nb_int*/ - 0, /*nb_reserved*/ + 0, /*nb_complex*/ proxy_float, /*nb_float*/ proxy_iadd, /*nb_inplace_add*/ proxy_isub, /*nb_inplace_subtract*/ diff --git a/PC/python3dll.c b/PC/python3dll.c index 84b3c735240b73..7bb8479240b7aa 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -168,6 +168,7 @@ EXPORT_FUNC(PyCodec_StrictErrors) EXPORT_FUNC(PyCodec_Unregister) EXPORT_FUNC(PyCodec_XMLCharRefReplaceErrors) EXPORT_FUNC(PyComplex_FromDoubles) +EXPORT_FUNC(PyComplex_FromString) EXPORT_FUNC(PyComplex_ImagAsDouble) EXPORT_FUNC(PyComplex_RealAsDouble) EXPORT_FUNC(PyDescr_NewClassMethod) @@ -430,6 +431,7 @@ EXPORT_FUNC(PyNumber_Add) EXPORT_FUNC(PyNumber_And) EXPORT_FUNC(PyNumber_AsSsize_t) EXPORT_FUNC(PyNumber_Check) +EXPORT_FUNC(PyNumber_Complex) EXPORT_FUNC(PyNumber_Divmod) EXPORT_FUNC(PyNumber_Float) EXPORT_FUNC(PyNumber_FloorDivide) diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv index 14dc5007b65861..29cec809d85409 100644 --- a/Tools/c-analyzer/cpython/ignored.tsv +++ b/Tools/c-analyzer/cpython/ignored.tsv @@ -467,6 +467,7 @@ Modules/_testcapimodule.c - BasicStaticTypes - Modules/_testcapimodule.c - num_basic_static_types_used - Modules/_testcapimodule.c - ContainerNoGC_members - Modules/_testcapimodule.c - ContainerNoGC_type - +Modules/_testcapimodule.c - OldComplexLikeType - Modules/_testcapimodule.c - FmData - Modules/_testcapimodule.c - FmHook - Modules/_testcapimodule.c - GenericAlias_Type -