mirror of https://github.com/python/cpython
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.
This commit is contained in:
parent
ac10947ba7
commit
0f2fa6150b
|
@ -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)
|
||||
|
||||
|
|
|
@ -77,8 +77,14 @@ class CAPIComplexTest(unittest.TestCase):
|
|||
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 @@ class CAPIComplexTest(unittest.TestCase):
|
|||
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())
|
||||
|
||||
# Function returns 0.0 anyway, see #109598
|
||||
self.assertEqual(imagasdouble(object()), 0.0)
|
||||
# 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)
|
||||
|
||||
self.assertRaises(TypeError, imagasdouble, object())
|
||||
|
||||
# CRASHES imagasdouble(NULL)
|
||||
|
||||
|
|
|
@ -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.
|
|
@ -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 *
|
||||
|
|
Loading…
Reference in New Issue