Objects/complexobject.c
cpython 3.14 @ ab2d84fe1023/Objects/complexobject.c
complexobject.c defines CPython's built-in complex type. It holds a Py_complex struct (two C double fields, real and imag) and implements all arithmetic directly in C before wrapping results in a PyComplexObject. The file also handles the __complex__ protocol, hash computation, and the public C API surface (PyComplex_RealAsDouble, PyComplex_ImagAsDouble, etc.).
Map
| Lines | Symbol | Role | gopy |
|---|---|---|---|
| 1-40 | includes, Py_TPFLAGS_BASETYPE | Header setup and type flags | not ported |
| 41-120 | c_sum, c_diff, c_prod, c_quot | Pure-C complex arithmetic on Py_complex | not ported |
| 121-200 | c_pow, c_abs | Power and absolute value for Py_complex | not ported |
| 201-280 | PyComplex_FromCComplex, PyComplex_FromDoubles | Allocate and return a PyComplexObject | not ported |
| 281-360 | PyComplex_RealAsDouble, PyComplex_ImagAsDouble | Extract components from any object via __complex__ | not ported |
| 361-480 | PyComplex_AsCComplex | Coerce an arbitrary Python object to Py_complex | not ported |
| 481-620 | complex_add, complex_sub, complex_mul, complex_div | PyNumberMethods slots delegating to c_* helpers | not ported |
| 621-720 | complex_pow, complex_neg, complex_pos, complex_abs | Remaining numeric slots | not ported |
| 721-800 | complex_bool, complex_richcompare | Boolean and ==/!= comparison (order comparisons raise TypeError) | not ported |
| 801-880 | complex_hash | Hash via _Py_HashDouble on real and imag parts | not ported |
| 881-980 | complex_repr, complex_format | String and __format__ output | not ported |
| 981-1060 | complex_new | tp_new: parse real and imag args, call __complex__ when needed | not ported |
| 1061-1100 | PyComplexType definition | PyTypeObject struct with all slots wired up | not ported |
Reading
Pure-C arithmetic layer (lines 41 to 200)
cpython 3.14 @ ab2d84fe1023/Objects/complexobject.c#L41-200
Before any Python object is involved, CPython does all complex arithmetic on bare Py_complex structs. This keeps the hot path free of reference counting and GIL concerns. The four basic operations follow textbook definitions; c_quot guards against division by zero by checking the magnitude of the denominator:
Py_complex
c_quot(Py_complex a, Py_complex b)
{
Py_complex r;
double abs_breal = b.real < 0 ? -b.real : b.real;
double abs_bimag = b.imag < 0 ? -b.imag : b.imag;
if (abs_breal >= abs_bimag) {
double ratio = b.imag / b.real;
double denom = b.real + b.imag * ratio;
r.real = (a.real + a.imag * ratio) / denom;
r.imag = (a.imag - a.real * ratio) / denom;
} else {
double ratio = b.real / b.imag;
double denom = b.real * ratio + b.imag;
r.real = (a.real * ratio + a.imag) / denom;
r.imag = (a.imag * ratio - a.real) / denom;
}
return r;
}
The two-branch algorithm avoids catastrophic cancellation when one component dominates the other. c_pow is more elaborate: it special-cases zero exponents, integer exponents, and uses cexp/clog for the general case.
Coercion and the __complex__ protocol (lines 361 to 480)
cpython 3.14 @ ab2d84fe1023/Objects/complexobject.c#L361-480
PyComplex_AsCComplex is the central coercion point. It first checks whether the argument is already a PyComplexObject; if so it returns the internal cval field directly. Otherwise it looks for a __complex__ method, calls it, and validates that the result is a complex instance:
Py_complex
PyComplex_AsCComplex(PyObject *op)
{
Py_complex cv;
PyObject *newop = NULL;
if (PyComplex_Check(op)) {
return ((PyComplexObject *)op)->cval;
}
/* Try __complex__ */
...
newop = _PyObject_CallMethod(op, &_Py_ID(__complex__), NULL);
if (!PyComplex_Check(newop)) {
PyErr_SetString(PyExc_TypeError,
"__complex__ should return a complex object");
...
}
cv = ((PyComplexObject *)newop)->cval;
...
return cv;
}
This is the mechanism that lets user-defined classes integrate with complex() and with arithmetic operators transparently.
complex_new and argument parsing (lines 981 to 1060)
cpython 3.14 @ ab2d84fe1023/Objects/complexobject.c#L981-1060
complex_new handles all valid call signatures: complex(), complex(x), complex(x, y), complex("3+4j"). The string path delegates to PyOS_string_to_double and a custom parser. The two-argument path calls PyComplex_AsCComplex on each argument independently and then adds the imaginary parts:
cr = PyComplex_AsCComplex(r);
...
ci = PyComplex_AsCComplex(i);
...
/* real part = cr.real - ci.imag, imag part = cr.imag + ci.real */
return PyComplex_FromDoubles(cr.real - ci.imag, cr.imag + ci.real);
The subtraction of ci.imag from the real part is the correct identity: complex(a+bj, c+dj) equals (a - d) + (b + c)j.
gopy mirror
complexobject.c has no counterpart in gopy yet. When ported, the natural home is objects/complex.go. The pure-C arithmetic layer maps cleanly to Go functions operating on a struct{ Real, Imag float64 }, and Go's math/cmplx package can cross-check correctness. The __complex__ protocol lookup fits into gopy's existing protocol.go pattern.
CPython 3.14 changes
CPython 3.14 introduced _colorize support for REPL output; complex_repr now produces highlighted output when running interactively. The hash algorithm itself did not change. The __complex__ protocol gained a deprecation path for objects that define __complex__ but return a non-complex type, matching the deprecation applied to __int__ and __float__ in earlier releases.