Repository navigation
Description
Bug report
Bug description
Since 3.14, the single-argument form complex(x) no longer calls x.__float__() when x is an instance of a float subclass that overrides __float__. The two-argument form, the keyword form and float(x) still call it, and 3.13 called it in every form.
class F(float):
def __float__(self):
return 42.0
x = F(1.0)
print(complex(x)) # 3.13: (42+0j) 3.14+: (1+0j)
print(complex(x, 0)) # (42+0j) on every version
print(complex(real=x)) # (42+0j) on every version
print(float(x)) # 42.0 on every version| 3.12.13 | 3.13.18 | 3.14.10 | 3.15.2 | 3.16.0a2 | |
|---|---|---|---|---|---|
complex(x) |
(42+0j) |
(42+0j) |
(1+0j) |
(1+0j) |
(1+0j) |
complex(x, 0) |
(42+0j) |
(42+0j) |
(42+0j) |
(42+0j) |
(42+0j) |
float(x) |
42.0 |
42.0 |
42.0 |
42.0 |
42.0 |
Because __float__ is no longer called, an exception it raises no longer propagates either: with def __float__(self): raise Exception, complex(x) raises on 3.13 and returns (1+0j) on 3.14.
The documentation describes the single-argument form of complex() in the same words it uses for float(): "For a general Python object x, complex(x) delegates to x.__complex__(). If __complex__ is not defined then it falls back to __float__." float(x) calls the override, and complex(x) did too up to 3.13.
The new result is the one complex.from_number(x) and float.from_number(x) give. Those, like math.sqrt() and other C-level conversions, read the stored value of a float instance through PyFloat_AsDouble() and have never consulted the override. This report is only about the constructor, whose forms now disagree with each other and with float(x).
Cause
GH-119620 (gh-109218, ef01e95, first released in 3.14.0a1) split the constructor and added a single-argument path, actual_complex_new() in Objects/complexobject.c. That path converts a real argument with PyFloat_AsDouble(arg), which returns the value of any float instance directly (PyFloat_Check) without calling nb_float:
cpython/Objects/complexobject.c
Lines 1138 to 1147 in b703b2c
| 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); | |
| } |
The two-argument path in complex_new_impl() keeps PyNumber_Float(r), which 3.13 used for every form and which calls nb_float unless the argument is an exact float:
cpython/Objects/complexobject.c
Line 1259 in b703b2c
| tmp = PyNumber_Float(r); |
The change is not mentioned in What's New in 3.14 or in the NEWS entry of gh-109218, and test_complex exercises __float__ only on a class that does not inherit from float (WithFloat), so no test covers a float subclass that overrides it.
Possible fix
Convert through PyNumber_Float() in the single-argument path as the two-argument path does, or, if the new behavior is intended, document that complex(x) does not consult __float__ of a float subclass.
CPython versions tested on:
3.14, 3.15, 3.16
Operating systems tested on:
Linux
Activity
skirpichev commentedon Nov 13, 2026
That seems related to #109311, i.e.: can we silently get value of the base type for numeric types of the stdlib?
If we can, probably new behavior is more appropriate. But as you correctly noted, float() constructor is inconsistent with that. If not, probably it's a valid issue.
As pointed out in the pr thread:
Same happens for math.sqrt(FloatSub()). I don't see good reasons to keep this inconsistency wrt complex/float constructors.
I also don't see good reasons to override
__float__/__complex__dunders in subclasses. (Any practical example appreciated.) Processing them will only introduce slowdown for handling subclasses. So, my suggestion is that we should align behavior of constructors wrt PyFloat_AsDouble/PyComplex_AsCComplex/etc. Those functions already have correct docs, for example (PyFloat_AsDouble):CC @serhiy-storchaka