diff --git a/Doc/c-api/complex.rst b/Doc/c-api/complex.rst index aa91b85d07fc7e..9951e055befe2a 100644 --- a/Doc/c-api/complex.rst +++ b/Doc/c-api/complex.rst @@ -101,6 +101,12 @@ Complex Number Objects Create a new Python complex number object from a C :c:type:`Py_complex` value. Return ``NULL`` with an exception set on error. +.. 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) 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 8bd3144f88aa71..3dbfc9c372bcec 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__ | +---------------------------------------------------------+-----------------------------------+---------------+ @@ -2588,7 +2588,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; @@ -2622,11 +2622,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 @@ -2645,7 +2640,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 7ad5f3ecfab5b4..6b9ee0b0201d3e 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -92,6 +92,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,, @@ -455,6 +456,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 80f3298701d249..daf68f89970732 100644 --- a/Include/abstract.h +++ b/Include/abstract.h @@ -574,6 +574,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 e2f87524c218b6..7d41d9b2a286b7 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 c3189a67cc7e2d..5ee17bcd9f385a 100644 --- a/Lib/test/test_capi/test_complex.py +++ b/Lib/test/test_capi/test_complex.py @@ -132,6 +132,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 @@ -294,6 +307,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 bdd8868529f632..997a3e13c35d41 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")) @@ -301,6 +305,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 cbec7e43a7c9fb..8262d1b4ce53b4 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -131,6 +131,7 @@ def test_windows_feature_macros(self): "PyCodec_Unregister", "PyCodec_XMLCharRefReplaceErrors", "PyComplex_FromDoubles", + "PyComplex_FromString", "PyComplex_ImagAsDouble", "PyComplex_RealAsDouble", "PyComplex_Type", @@ -487,6 +488,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 4a03cc76f5e1e9..f28aedc9e7def0 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] @@ -2577,7 +2579,10 @@ added = '3.14' [function.Py_PACK_VERSION] added = '3.14' - +[function.PyComplex_FromString] + added = '3.14' +[function.PyNumber_Complex] + added = '3.14' [function.PySys_GetAttr] added = '3.15' [function.PySys_GetAttrString] diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 46c4f57984b0df..9ecf5b36ef5c4b 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -3073,7 +3073,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 04b6695f8af06a..994f2bf6bc6e5a 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -5713,15 +5713,8 @@ _decimal_Decimal___ceil___impl(PyObject *self, PyTypeObject *cls) return dec_as_long(self, context, MPD_ROUND_CEILING); } -/*[clinic input] -_decimal.Decimal.__complex__ - -Convert this value to exact type complex. -[clinic start generated code]*/ - static PyObject * -_decimal_Decimal___complex___impl(PyObject *self) -/*[clinic end generated code: output=c9b5b4a9fdebc912 input=6b11c6f20af7061a]*/ +dec_complex(PyObject *self) { PyObject *f; double x; @@ -6087,7 +6080,6 @@ static PyMethodDef dec_methods [] = _DECIMAL_DECIMAL___CEIL___METHODDEF _DECIMAL_DECIMAL___FLOOR___METHODDEF _DECIMAL_DECIMAL___TRUNC___METHODDEF - _DECIMAL_DECIMAL___COMPLEX___METHODDEF _DECIMAL_DECIMAL___SIZEOF___METHODDEF { NULL, NULL, 1 } @@ -6120,6 +6112,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/_decimal/clinic/_decimal.c.h b/Modules/_decimal/clinic/_decimal.c.h index ccfbf63a7cead5..096d704c40d3ef 100644 --- a/Modules/_decimal/clinic/_decimal.c.h +++ b/Modules/_decimal/clinic/_decimal.c.h @@ -3862,24 +3862,6 @@ _decimal_Decimal___ceil__(PyObject *self, PyTypeObject *cls, PyObject *const *ar return _decimal_Decimal___ceil___impl(self, cls); } -PyDoc_STRVAR(_decimal_Decimal___complex____doc__, -"__complex__($self, /)\n" -"--\n" -"\n" -"Convert this value to exact type complex."); - -#define _DECIMAL_DECIMAL___COMPLEX___METHODDEF \ - {"__complex__", (PyCFunction)_decimal_Decimal___complex__, METH_NOARGS, _decimal_Decimal___complex____doc__}, - -static PyObject * -_decimal_Decimal___complex___impl(PyObject *self); - -static PyObject * -_decimal_Decimal___complex__(PyObject *self, PyObject *Py_UNUSED(ignored)) -{ - return _decimal_Decimal___complex___impl(self); -} - PyDoc_STRVAR(_decimal_Decimal___copy____doc__, "__copy__($self, /)\n" "--\n" @@ -6896,4 +6878,4 @@ _decimal_Context_same_quantum(PyObject *context, PyTypeObject *cls, PyObject *co #ifndef _DECIMAL_CONTEXT_APPLY_METHODDEF #define _DECIMAL_CONTEXT_APPLY_METHODDEF #endif /* !defined(_DECIMAL_CONTEXT_APPLY_METHODDEF) */ -/*[clinic end generated code: output=e938de3a355a353a input=a9049054013a1b77]*/ +/*[clinic end generated code: output=a0464d3d6edd5f6a input=a9049054013a1b77]*/ 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 4e73be20e1b709..c2ee6ab8ab146e 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2700,7 +2700,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 */ @@ -3191,6 +3191,27 @@ 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, +}; + typedef struct { PyObject_VAR_HEAD } ManagedDictObject; @@ -3354,6 +3375,11 @@ _testcapi_exec(PyObject *m) return -1; } + if (PyType_Ready(&OldComplexLikeType) < 0) { + return -1; + } + Py_INCREF(&OldComplexLikeType); + PyModule_AddObject(m, "old_complex_like", (PyObject *)&OldComplexLikeType); PyObject *managed_dict_type = create_managed_dict_type(); if (managed_dict_type == NULL) { return -1; 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 8adad8407d04d4..edc621d42c2975 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -907,7 +907,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 */ @@ -1649,6 +1649,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 03fc137c345734..6666bdb3e074cd 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -504,34 +504,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, - "%T.__complex__() must return a complex, not %T", - op, res); - Py_DECREF(res); - return NULL; - } - /* Issue #29894: warn if 'res' not of exact type complex. */ - if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1, - "%T.__complex__() must return a complex, not %T. " - "The ability to return an instance of a strict subclass of complex " - "is deprecated, and may be removed in a future version of Python.", - op, res)) { - 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 @@ -925,25 +935,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) { @@ -1097,6 +1099,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. @@ -1110,61 +1118,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; } @@ -1345,7 +1316,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 */ @@ -1377,7 +1347,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 1fefb12803ec19..d78a0ae772a9f6 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -1838,7 +1838,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 5eb4063f861f7a..cadeb99ccbaa9d 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -6615,7 +6615,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 0540112d7d2acf..c86d0e0e99ac77 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2242,7 +2242,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 d8340499be5aae..88682b86e5d857 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -2540,7 +2540,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 9398bcb29c83e4..7fad001dac03fa 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -10516,6 +10516,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 *) @@ -11339,6 +11340,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 61fa3ddad0bfd8..001d9844df7428 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 05c86e6d5924d4..55ef7b41ee1886 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -169,6 +169,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) @@ -434,6 +435,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 c3b13d69f0de8e..e24d6890167ba9 100644 --- a/Tools/c-analyzer/cpython/ignored.tsv +++ b/Tools/c-analyzer/cpython/ignored.tsv @@ -473,6 +473,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 -