Repository navigation
Deprecate PyComplexObject.cval and soft-deprecate _Py_c*() API #128813
Description
Activity
- addedtype-featureA feature request or enhancementA feature request or enhancement
on Jan 14, 2025 There is an alternative: the C11
double complextype.
AFAICS, the remaining reasons for Python to expose thePyComplexstruct are:- backwards compatibility
- interop with other languages (i.e. FFI), including older C. The API should be usable with only a limited set of basic C types (
doublebut not_Complex). But, we don't need to provide arithmetic for this use case.
If users want to use Python API for arithmetic, they should use
PyNumber_*.IMO, we should instead add
PyComplex_FromDoubleComplex&PyComplex_AsDoubleComplex(but theFrom/Asfunctions we already have are more important).It's a question for the C API WG though, if not for wider discussion.
Reacted by Sergey B KirpichevThere is an alternative: the C11 double complex type.
Not all compilers support that type. For msvc we have
_Dcomplex, that might be an alternative.IMO, we should instead add PyComplex_FromDoubleComplex & PyComplex_AsDoubleComplex (but the From/As functions we already have are more important).
Maybe rather just one
PyComplex_AsDoubles.On another hand, it seems that all PEP 11 platforms have complex numbers in some form. And Py_complex can be coerced both to
_Dcomplexanddouble _Complex.So, the plan is:
- Document that
Py_complexcan be coerced to native complex types. Thus,PyComplex_AsCComplex()can be used to extract complex type. - Remove recently added mixed-mode arithmetic functions from the public API.
- Deprecate old arithmetic functions.
How this sounds, should this be discussed in the C API WG? CC @serhiy-storchaka
We also have API functions to extract real or imaginary components. Maybe we can also hide details of the
Py_complexstructure? E.g. instead we can say aboutPy_complexsomething like C standard says: "has the same representation and alignment requirements as an array type containing exactly two doubles". Then with a wider adoption of the Annex G we can switch to native complex type.Reacted by Petr Viktorin- Document that
AFAIK, MSVC's
_Dcomplexis the C11double complextype. (You do need to include<complex.h>for the definition, though, which we might not want to do from<Python.h>.)Maybe we can also hide details of the Py_complex structure? E.g. instead we can say about
Py_complexsomething like C standard says: "has the same representation and alignment requirements as an array type containing exactly two doubles".No,
Py_complexis a good sructure for inerop. IMO, we should keep it (for external API -- import/export).
Defining the struct in English, rather than in C, doesn't really bring any benefits :)Then with a wider adoption of the Annex G we can switch to native complex type.
We should add the C complex type. The C API is not just for C and C++ :)
How this sounds, should this be discussed in the C API WG? CC @serhiy-storchaka
Sounds good to me; do ask the WG though.
MSVC's _Dcomplex is the C11 double complex type.
No, but it's memory layout seems compatible with double complex (and Py_complex). However, you can't use usual arithmetic ops with
_Dcomplex. Thus it's something completely different from Annex G complex type:)No, Py_complex is a good sructure for inerop. IMO, we should keep it (for external API -- import/export).
I'm not saying it's bad. But keeping fields of this structure public - blocks us from using Annex G double complex instead of Py_complex. I'm not sure it's wise to close this door.
Defining the struct in English, rather than in C, doesn't really bring any benefits :)
double _Complextype has no real/imag fields. If we don't specify thatPy_complextype has these fields - we can use native complex type instead. Can we count this as a benefit? (And it will be possible to extract real/imag components without using CPython C-API.)We should not use the same prefix
PyComplex_forPyObject *based API andPy_complexbased API. This will lead to confusion and may even create name conflicts.As for passing the
Py_complexarguments by pointer instead of by value, it conflicts with the existing API.PyComplex_FromCComplex()takes thePy_complexargument by values andPyComplex_AsCComplex()returns aPy_complexvalue. Modern compilers should be pretty efficient in passing small structures by value, so I do not expect significant benefit if any.The structure of
Py_complexis documented, so we cannot make it an alias of other complex type which is not compatible withtypedef struct { double real; double imag; } Py_complex;
We should also take into account compatibility with C++.
it's memory layout seems compatible with double complex (and Py_complex).
If the layout is compatible, I'd expect that
CMPLX(pycomplex.real, pycomplex.imag)and(Py_complex){creal(c11complex), cimag(c11complex)}could have no runtime cost.
Is that not the case?I'm not saying it's bad. But keeping fields of this structure public - blocks us from using Annex G double complex instead of Py_complex. I'm not sure it's wise to close this door.
Hm, could we switch away from using
Py_complexinternally, and keep it for theFrom/AsAPI only?
That is, deprecate public usePyComplexObject, and after a few (or many) releases, make the struct internal and switch fromcvalto adouble complexmember.We should not use the same prefix PyComplex_ for PyObject * based API and Py_complex based API.
Sure. Naming like
Py_complex_add()andPy_complex_add_real()sounds ok for you?But I like @encukou idea to drop arithmetic functions from the public API. People could use native complex types on their platform instead. One minor note, custom functions do make sense if our support for complex arithmetic will go beyond the C standard (or, rather, beyond it's implementations). As in my proposal, for example: https://discuss.python.org/t/77073.
for passing the Py_complex arguments by pointer instead of by value, it conflicts with the existing AP [...] I do not expect significant benefit if any.
As I said, my preliminary benchmarks show no measurable effect.
If the layout is compatible, I'd expect that CMPLX(pycomplex.real, pycomplex.imag) and (Py_complex){creal(c11complex), cimag(c11complex)} could have no runtime cost.
No, I meant you can do something like this (on Windows you can use
_Dcomplex, as it looks from docs):Py_complex z = {.real=1.25, .imag=-0.5}; // say this come from PyComplex_AsCComplex() double complex *zn = (double complex *)(&z); // in memory layout is same printf("(%lf%+lf)\n", creal(*zn), cimag(*zn)); // do math with native complex type
In principle, this seems to be working in both directions (i.e. you can pass
double complextoPyComplex_FromCComplex()) on most platforms. But that's not backed by the C standard.Hm, could we switch away from using Py_complex internally, and keep it for the From/As API only? That is, deprecate public use PyComplexObject
BTW, PyComplexObject structure fields aren't documented, just as for PyFloatObject.
No, definitely don't cast between
Py_complexanddouble complex. Even if it happens to work, a future compiler update can break that. And if it's faster than a proper conversion, that's a missing optimization opportunity for the compiler.BTW, PyComplexObject structure fields aren't documented, just as for PyFloatObject.
That doesn't really matter. Per PEP-387, “if something is not documented at all, it is not automatically considered private”.
Even if it happens to work, a future compiler update can break that.
No, it's something we can rely on, thanks to the C standard; it says: "Each complex type has the same representation and alignment requirements as an array type containing exactly two elements of the corresponding real type; the first element is equal to the real part, and the second element to the imaginary part, of the complex number." (c) C17 §6.2.5p13.
Structs are not arrays; they may have padding between the members (§6.7.2.1.15). Unlikely in production, but a future sanitizer might well catch it.
One minor note, custom functions do make sense if our support for complex arithmetic will go beyond the C standard (or, rather, beyond it's implementations).
I don't think CPython should not provide and maintain public C API for that. This is not the place to fill holes in libc.
Reacted by Sergey B Kirpichev and Erlend E. AaslandStructs are not arrays; they may have padding between the members (§6.7.2.1.15).
Thanks, I miss this. Looks not too likely for simple structure with two doubles, but...
I don't think CPython should not provide and maintain public C API for that. This is not the place to fill holes in libc.
Maintenance cost shouldn't be too high. Without this people lose opportunity to add new mathematical functions with C-API.
Of course, it's a hypothetical scenario so far. In present shape our low-level function should be equivalent (modulo errors somewhere) to most existing Annex G implementations.
I'll open a C-API WG issue, as you suggested.
13 remaining items
With #128813 I think, that this issue can be closed.
One minor point, that may worth a separate issue: provide API to import/export from/to native complex type (double complex), if available. Let me know if this can be solved here.
I think we're fine. Converting via two doubles (
PyComplex_FromDoubles) or viaPy_complex(the export format) is a bit inconvenient, but (AFAIK) shouldn't have a performance impact.
API that requires optional C features is its own can of worms.Thank you for seeing this through!
- added a commit that references this issue
on Aug 12, 2025 - added 3 commits that reference this issue
on Aug 19, 2025
Proposal:
Suggested by @vstinner in #124829 (comment).
I would agree, as these routines could be useful (and it seems, they are used by few projects) to implement mathematical functions just like in the cmath module. No alternatives exist.
It's also suggested to use a different convention for arguments: currently we pass them by value. We could use pointers to Py_complex struct instead. (Though my quick tests shows no measurable difference.)
Also, we should decide on naming. For
_Py_c_sum()-PyComplex_Add()was suggested. But this looks misleading, asPyComplex_is a prefix for functions, operating withPyObject*arguments. Perhaps, rather we could instead usePy_complex_add()andPy_complex_add_real()(in the GNU GSL style). Current semi-private functions should be deprecated.Edit:
The C-API WG decision: Soft-deprecate the Py_c*() functions and Deprecate PyComplexObject.cval.
Previous discussions:
Other:
Linked PRs