https://github.com/python/cpython/commit/0f2fa6150baf111a6c69d5d491c95c3c2ee60eaf
commit: 0f2fa6150baf111a6c69d5d491c95c3c2ee60eaf
branch: main
author: Sergey B Kirpichev <[email protected]>
committer: encukou <[email protected]>
date: 2024-01-15T16:04:17+01:00
summary:

gh-109598: make PyComplex_RealAsDouble/ImagAsDouble use __complex__ (GH-109647)

`PyComplex_RealAsDouble()`/`PyComplex_ImagAsDouble` now try to convert
an object to a `complex` instance using its `__complex__()` method
before falling back to the ``__float__()`` method.

PyComplex_ImagAsDouble() also will not silently return 0.0 for
non-complex types anymore.  Instead we try to call PyFloat_AsDouble()
and return 0.0 only if this call is successful.

files:
A Misc/NEWS.d/next/Core and 
Builtins/2023-09-21-11-54-28.gh-issue-109598.CRidSy.rst
M Doc/c-api/complex.rst
M Lib/test/test_capi/test_complex.py
M Objects/complexobject.c

diff --git a/Doc/c-api/complex.rst b/Doc/c-api/complex.rst
index e3fd001c599c80..5a0474869071d9 100644
--- a/Doc/c-api/complex.rst
+++ b/Doc/c-api/complex.rst
@@ -117,11 +117,29 @@ Complex Numbers as Python Objects
 
    Return the real part of *op* as a C :c:expr:`double`.
 
+   If *op* is not a Python complex number object but has a
+   :meth:`~object.__complex__` method, this method will first be called to
+   convert *op* to a Python complex number object.  If :meth:`!__complex__` is
+   not defined then it falls back to call :c:func:`PyFloat_AsDouble` and
+   returns its result.  Upon failure, this method returns ``-1.0``, so one
+   should call :c:func:`PyErr_Occurred` to check for errors.
+
+   .. versionchanged:: 3.13
+      Use :meth:`~object.__complex__` if available.
 
 .. c:function:: double PyComplex_ImagAsDouble(PyObject *op)
 
    Return the imaginary part of *op* as a C :c:expr:`double`.
 
+   If *op* is not a Python complex number object but has a
+   :meth:`~object.__complex__` method, this method will first be called to
+   convert *op* to a Python complex number object.  If :meth:`!__complex__` is
+   not defined then it falls back to call :c:func:`PyFloat_AsDouble` and
+   returns ``0.0`` on success.  Upon failure, this method returns ``-1.0``, so
+   one should call :c:func:`PyErr_Occurred` to check for errors.
+
+   .. versionchanged:: 3.13
+      Use :meth:`~object.__complex__` if available.
 
 .. c:function:: Py_complex PyComplex_AsCComplex(PyObject *op)
 
diff --git a/Lib/test/test_capi/test_complex.py 
b/Lib/test/test_capi/test_complex.py
index d6fc1f077c40aa..a5b59558e7f851 100644
--- a/Lib/test/test_capi/test_complex.py
+++ b/Lib/test/test_capi/test_complex.py
@@ -77,8 +77,14 @@ def test_realasdouble(self):
         self.assertEqual(realasdouble(FloatSubclass(4.25)), 4.25)
 
         # Test types with __complex__ dunder method
-        # Function doesn't support classes with __complex__ dunder, see #109598
-        self.assertRaises(TypeError, realasdouble, Complex())
+        self.assertEqual(realasdouble(Complex()), 4.25)
+        self.assertRaises(TypeError, realasdouble, BadComplex())
+        with self.assertWarns(DeprecationWarning):
+            self.assertEqual(realasdouble(BadComplex2()), 4.25)
+        with warnings.catch_warnings():
+            warnings.simplefilter("error", DeprecationWarning)
+            self.assertRaises(DeprecationWarning, realasdouble, BadComplex2())
+        self.assertRaises(RuntimeError, realasdouble, BadComplex3())
 
         # Test types with __float__ dunder method
         self.assertEqual(realasdouble(Float()), 4.25)
@@ -104,11 +110,22 @@ def test_imagasdouble(self):
         self.assertEqual(imagasdouble(FloatSubclass(4.25)), 0.0)
 
         # Test types with __complex__ dunder method
-        # Function doesn't support classes with __complex__ dunder, see #109598
-        self.assertEqual(imagasdouble(Complex()), 0.0)
+        self.assertEqual(imagasdouble(Complex()), 0.5)
+        self.assertRaises(TypeError, imagasdouble, BadComplex())
+        with self.assertWarns(DeprecationWarning):
+            self.assertEqual(imagasdouble(BadComplex2()), 0.5)
+        with warnings.catch_warnings():
+            warnings.simplefilter("error", DeprecationWarning)
+            self.assertRaises(DeprecationWarning, imagasdouble, BadComplex2())
+        self.assertRaises(RuntimeError, imagasdouble, BadComplex3())
+
+        # Test types with __float__ dunder method
+        self.assertEqual(imagasdouble(Float()), 0.0)
+        self.assertRaises(TypeError, imagasdouble, BadFloat())
+        with self.assertWarns(DeprecationWarning):
+            self.assertEqual(imagasdouble(BadFloat2()), 0.0)
 
-        # Function returns 0.0 anyway, see #109598
-        self.assertEqual(imagasdouble(object()), 0.0)
+        self.assertRaises(TypeError, imagasdouble, object())
 
         # CRASHES imagasdouble(NULL)
 
diff --git a/Misc/NEWS.d/next/Core and 
Builtins/2023-09-21-11-54-28.gh-issue-109598.CRidSy.rst b/Misc/NEWS.d/next/Core 
and Builtins/2023-09-21-11-54-28.gh-issue-109598.CRidSy.rst
new file mode 100644
index 00000000000000..3eedc45b1fbf34
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and 
Builtins/2023-09-21-11-54-28.gh-issue-109598.CRidSy.rst 
@@ -0,0 +1,3 @@
+:c:func:`PyComplex_RealAsDouble`/:c:func:`PyComplex_ImagAsDouble` now tries to
+convert an object to a :class:`complex` instance using its ``__complex__()`` 
method
+before falling back to the ``__float__()`` method.  Patch by Sergey B 
Kirpichev.
diff --git a/Objects/complexobject.c b/Objects/complexobject.c
index 0e96f54584677c..d8b0e84da5df4a 100644
--- a/Objects/complexobject.c
+++ b/Objects/complexobject.c
@@ -256,26 +256,51 @@ PyComplex_FromDoubles(double real, double imag)
     return PyComplex_FromCComplex(c);
 }
 
+static PyObject * try_complex_special_method(PyObject *);
+
 double
 PyComplex_RealAsDouble(PyObject *op)
 {
+    double real = -1.0;
+
     if (PyComplex_Check(op)) {
-        return ((PyComplexObject *)op)->cval.real;
+        real = ((PyComplexObject *)op)->cval.real;
     }
     else {
-        return PyFloat_AsDouble(op);
+        PyObject* newop = try_complex_special_method(op);
+        if (newop) {
+            real = ((PyComplexObject *)newop)->cval.real;
+            Py_DECREF(newop);
+        } else if (!PyErr_Occurred()) {
+            real = PyFloat_AsDouble(op);
+        }
     }
+
+    return real;
 }
 
 double
 PyComplex_ImagAsDouble(PyObject *op)
 {
+    double imag = -1.0;
+
     if (PyComplex_Check(op)) {
-        return ((PyComplexObject *)op)->cval.imag;
+        imag = ((PyComplexObject *)op)->cval.imag;
     }
     else {
-        return 0.0;
+        PyObject* newop = try_complex_special_method(op);
+        if (newop) {
+            imag = ((PyComplexObject *)newop)->cval.imag;
+            Py_DECREF(newop);
+        } else if (!PyErr_Occurred()) {
+            PyFloat_AsDouble(op);
+            if (!PyErr_Occurred()) {
+                imag = 0.0;
+            }
+        }
     }
+
+    return imag;
 }
 
 static PyObject *

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to