pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/python/cpython/commit/0f2fa6150baf111a6c69d5d491c95c3c2ee60eaf

b55097560d244c08.css" /> gh-109598: make PyComplex_RealAsDouble/ImagAsDouble use __complex__ (… · python/cpython@0f2fa61 · GitHub
Skip to content

Commit 0f2fa61

Browse files
authored
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.
1 parent ac10947 commit 0f2fa61

File tree

4 files changed

+73
-10
lines changed

4 files changed

+73
-10
lines changed

Doc/c-api/complex.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,29 @@ Complex Numbers as Python Objects
117117
118118
Return the real part of *op* as a C :c:expr:`double`.
119119
120+
If *op* is not a Python complex number object but has a
121+
:meth:`~object.__complex__` method, this method will first be called to
122+
convert *op* to a Python complex number object. If :meth:`!__complex__` is
123+
not defined then it falls back to call :c:func:`PyFloat_AsDouble` and
124+
returns its result. Upon failure, this method returns ``-1.0``, so one
125+
should call :c:func:`PyErr_Occurred` to check for errors.
126+
127+
.. versionchanged:: 3.13
128+
Use :meth:`~object.__complex__` if available.
120129
121130
.. c:function:: double PyComplex_ImagAsDouble(PyObject *op)
122131
123132
Return the imaginary part of *op* as a C :c:expr:`double`.
124133
134+
If *op* is not a Python complex number object but has a
135+
:meth:`~object.__complex__` method, this method will first be called to
136+
convert *op* to a Python complex number object. If :meth:`!__complex__` is
137+
not defined then it falls back to call :c:func:`PyFloat_AsDouble` and
138+
returns ``0.0`` on success. Upon failure, this method returns ``-1.0``, so
139+
one should call :c:func:`PyErr_Occurred` to check for errors.
140+
141+
.. versionchanged:: 3.13
142+
Use :meth:`~object.__complex__` if available.
125143
126144
.. c:function:: Py_complex PyComplex_AsCComplex(PyObject *op)
127145

Lib/test/test_capi/test_complex.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,14 @@ def test_realasdouble(self):
7777
self.assertEqual(realasdouble(FloatSubclass(4.25)), 4.25)
7878

7979
# Test types with __complex__ dunder method
80-
# Function doesn't support classes with __complex__ dunder, see #109598
81-
self.assertRaises(TypeError, realasdouble, Complex())
80+
self.assertEqual(realasdouble(Complex()), 4.25)
81+
self.assertRaises(TypeError, realasdouble, BadComplex())
82+
with self.assertWarns(DeprecationWarning):
83+
self.assertEqual(realasdouble(BadComplex2()), 4.25)
84+
with warnings.catch_warnings():
85+
warnings.simplefilter("error", DeprecationWarning)
86+
self.assertRaises(DeprecationWarning, realasdouble, BadComplex2())
87+
self.assertRaises(RuntimeError, realasdouble, BadComplex3())
8288

8389
# Test types with __float__ dunder method
8490
self.assertEqual(realasdouble(Float()), 4.25)
@@ -104,11 +110,22 @@ def test_imagasdouble(self):
104110
self.assertEqual(imagasdouble(FloatSubclass(4.25)), 0.0)
105111

106112
# Test types with __complex__ dunder method
107-
# Function doesn't support classes with __complex__ dunder, see #109598
108-
self.assertEqual(imagasdouble(Complex()), 0.0)
113+
self.assertEqual(imagasdouble(Complex()), 0.5)
114+
self.assertRaises(TypeError, imagasdouble, BadComplex())
115+
with self.assertWarns(DeprecationWarning):
116+
self.assertEqual(imagasdouble(BadComplex2()), 0.5)
117+
with warnings.catch_warnings():
118+
warnings.simplefilter("error", DeprecationWarning)
119+
self.assertRaises(DeprecationWarning, imagasdouble, BadComplex2())
120+
self.assertRaises(RuntimeError, imagasdouble, BadComplex3())
121+
122+
# Test types with __float__ dunder method
123+
self.assertEqual(imagasdouble(Float()), 0.0)
124+
self.assertRaises(TypeError, imagasdouble, BadFloat())
125+
with self.assertWarns(DeprecationWarning):
126+
self.assertEqual(imagasdouble(BadFloat2()), 0.0)
109127

110-
# Function returns 0.0 anyway, see #109598
111-
self.assertEqual(imagasdouble(object()), 0.0)
128+
self.assertRaises(TypeError, imagasdouble, object())
112129

113130
# CRASHES imagasdouble(NULL)
114131

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:c:func:`PyComplex_RealAsDouble`/:c:func:`PyComplex_ImagAsDouble` now tries to
2+
convert an object to a :class:`complex` instance using its ``__complex__()`` method
3+
before falling back to the ``__float__()`` method. Patch by Sergey B Kirpichev.

Objects/complexobject.c

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -256,26 +256,51 @@ PyComplex_FromDoubles(double real, double imag)
256256
return PyComplex_FromCComplex(c);
257257
}
258258

259+
static PyObject * try_complex_special_method(PyObject *);
260+
259261
double
260262
PyComplex_RealAsDouble(PyObject *op)
261263
{
264+
double real = -1.0;
265+
262266
if (PyComplex_Check(op)) {
263-
return ((PyComplexObject *)op)->cval.real;
267+
real = ((PyComplexObject *)op)->cval.real;
264268
}
265269
else {
266-
return PyFloat_AsDouble(op);
270+
PyObject* newop = try_complex_special_method(op);
271+
if (newop) {
272+
real = ((PyComplexObject *)newop)->cval.real;
273+
Py_DECREF(newop);
274+
} else if (!PyErr_Occurred()) {
275+
real = PyFloat_AsDouble(op);
276+
}
267277
}
278+
279+
return real;
268280
}
269281

270282
double
271283
PyComplex_ImagAsDouble(PyObject *op)
272284
{
285+
double imag = -1.0;
286+
273287
if (PyComplex_Check(op)) {
274-
return ((PyComplexObject *)op)->cval.imag;
288+
imag = ((PyComplexObject *)op)->cval.imag;
275289
}
276290
else {
277-
return 0.0;
291+
PyObject* newop = try_complex_special_method(op);
292+
if (newop) {
293+
imag = ((PyComplexObject *)newop)->cval.imag;
294+
Py_DECREF(newop);
295+
} else if (!PyErr_Occurred()) {
296+
PyFloat_AsDouble(op);
297+
if (!PyErr_Occurred()) {
298+
imag = 0.0;
299+
}
300+
}
278301
}
302+
303+
return imag;
279304
}
280305

281306
static PyObject *

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





Check this box to remove all script contents from the fetched content.



Check this box to remove all images from the fetched content.


Check this box to remove all CSS styles from the fetched content.


Check this box to keep images inefficiently compressed and original size.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy