首页
/ CPython C API 复数对象深度指南:Py_complex 表示、PyComplex 函数族与 _Py_c_* 运算函数

CPython C API 复数对象深度指南:Py_complex 表示、PyComplex 函数族与 _Py_c_* 运算函数

2026-09-06 18:46:56作者:宣海椒Queenly

本文基于 CPython 官方文档 Doc/c-api/complex.rst(Complex Number Objects 一章)展开,面向 C 扩展开发者,系统讲解如何在 C 层面创建、检查、转换 CPython 的 complex 对象,以及底层 Py_complex 表示与一组复数算术函数的工作方式。读完本文,你将掌握 PyComplex_FromDoublesPyComplex_AsCComplex 等核心 API 的完整语义与错误处理约定,理解各 API 从 3.8 到 3.15+ 的演进与弃用路线,并能结合 Objects/complexobject.c 的源码与 Lib/test/test_capi/test_complex.py 的测试用例验证其行为边界(如 EDOM/ERANGE 语义)。

1. 对象模型:PyComplexObject 与 Py_complex 导出格式

CPython 在 C API 中通过两个类型描述复数:PyComplexObject(Python 对象)与 Py_complex(C 层导出格式)。

1.1 PyComplexObject:PyObject 的复数子类

PyComplexObjectPyObject 的子类型,表示一个 Python 复数对象。其结构定义在 Include/cpython/complexobject.h

typedef struct {
    PyObject_HEAD
    Py_complex cval;
} PyComplexObject;

文档同时说明了 cval 成员的弃用路线:

  • PyComplexObject 唯一的数据成员是 cval,类型为 C 的 Py_complex 表示;
  • 该成员自 Python 3.15 起弃用、预定在 3.20 移除(文档标注为 deprecated-removed 3.15 3.20),官方建议改用 PyComplex_AsCComplexPyComplex_FromCComplex 完成 Python complex 对象与 C Py_complex 表示之间的双向转换。该弃用条目也收录在 Doc/deprecations/c-api-pending-removal-in-3.20.rst 中。

实践含义:在新代码中不要直接访问 ((PyComplexObject *)op)->cval,而应统一走转换函数。这样即使未来内部表示变化(例如精度或布局调整),你的扩展仍然稳定。

1.2 Py_complex:复数的 C 层“导出格式”

文档给出的结构定义如下,源码实现在 Include/cpython/complexobject.h

typedef struct {
    double real;
    double imag;
} Py_complex;

注意它是双精度 double 的实部/虚部分量,且不继承 PyObject——它只是一个纯值类型,因此所有以它为参数或返回值的函数都按值传递(by value),而不是通过指针解引用。

1.3 PyComplex_Type 与类型检查

PyComplex_Type 是一个 PyTypeObject 实例,代表 Python 复数类型,与 Python 层的内建 complex 是同一个对象。声明见 Include/complexobject.h,具体 PyTypeObject 定义(含 tp_newtp_hashtp_as_number 等槽位)位于 Objects/complexobject.c

配套的类型检查函数:

函数 语义 是否总成功
int PyComplex_Check(PyObject *p) 参数是 PyComplexObject 或其子类型 时返回真
int PyComplex_CheckExact(PyObject *p) 参数是 PyComplexObject不是其子类型时返回真

Include/complexobject.h 中,两者被定义为宏:

#define PyComplex_Check(op) PyObject_TypeCheck((op), &PyComplex_Type)
#define PyComplex_CheckExact(op) Py_IS_TYPE((op), &PyComplex_Type)

官方 C API 测试 Lib/test/test_capi/test_complex.py 验证了这一点:check(1+2j)check(ComplexSubclass(1+2j)) 均为真,而 checkexact(ComplexSubclass(1+2j)) 为假;对 intfloat、普通 object 均返回假。测试中还明确标注 check(NULL) 会崩溃,即这两个函数不接受 NULL 参数

2. 创建复数对象:PyComplex_FromDoubles 与 PyComplex_FromCComplex

2.1 PyComplex_FromDoubles

PyObject* PyComplex_FromDoubles(double real, double imag);

realimag 两个 double 创建一个新的 PyComplexObject;出错时返回 NULL 并设置异常。

从源码看(Objects/complexobject.c),它只是把两个 double 打包成 Py_complex 后委托给 PyComplex_FromCComplex,因此两条路径共享同一套分配逻辑。

2.2 PyComplex_FromCComplex 与对象缓存(freelist)

PyObject* PyComplex_FromCComplex(Py_complex v);

从 C Py_complex 值创建新的 Python 复数对象;出错时返回 NULL 并设置异常。

其实现(Objects/complexobject.c)值得注意——CPython 为 complex 对象维护了一个 freelist 缓存:

PyObject *
PyComplex_FromCComplex(Py_complex cval)
{
    PyComplexObject *op = _Py_FREELIST_POP(PyComplexObject, complexes);
    if (op == NULL) {
        /* Inline PyObject_New */
        op = PyObject_Malloc(sizeof(PyComplexObject));
        if (op == NULL) {
            return PyErr_NoMemory();
        }
        _PyObject_Init((PyObject*)op, &PyComplex_Type);
    }
    op->cval = cval;
    return (PyObject *) op;
}

即:优先从 complexes 缓存中复用刚释放的 PyComplexObject,缓存未命中才真正 PyObject_Malloc。配套的释放逻辑在 complex_deallocObjects/complexobject.c)中:只有精确类型complexPyComplex_CheckExact 为真)才会放回 freelist,子类型实例走 tp_free 常规释放。这一机制解释了为什么复数对象的高频创建/销毁(如数值计算扩展)开销较低。

测试印证:Lib/test/test_capi/test_complex.pycomplex_fromccomplex(1+2j) == 1.0+2.0jcomplex_fromdoubles(1.0, 2.0) == 1.0+2.0j

3. 读取复数值:PyComplex_RealAsDouble / PyComplex_ImagAsDouble / PyComplex_AsCComplex

这三个函数是 Python 对象到 C 数值的读路径。它们的关键共性是:参数不必是精确的 complex 类型,API 会按一套“数值协议”尽力转换,且失败时有明确的可检测约定。

3.1 PyComplex_RealAsDouble

double PyComplex_RealAsDouble(PyObject *op);

返回 op 的实部(C double)。转换规则:

  1. op 是复数对象(含子类型)时,直接取实部;
  2. 否则若 op 定义了 __complex__ 方法,先调用它op 转换为复数对象(Python 3.13 起引入此行为,见文档 versionchanged 3.13);
  3. 若没有定义 __complex__,则回退调用 PyFloat_AsDouble 并返回其结果。

失败时返回 -1.0 并设置异常,因此必须调用 PyErr_Occurred() 检查错误(因为 -1.0 本身也可能是合法的实部值)。

3.2 PyComplex_ImagAsDouble

double PyComplex_ImagAsDouble(PyObject *op);

语义与实部版本对称:非复数对象先尝试 __complex__(3.13 起),否则回退到 PyFloat_AsDouble成功时返回虚部 0.0。失败同样返回 -1.0 并设置异常,需以 PyErr_Occurred() 判定。

源码中的两条路径在 Objects/complexobject.c 实现,核心逻辑是:先 PyComplex_Check(op) 快速命中;未命中则调用 try_complex_special_method(op) 尝试 __complex__,若该方法未定义且无异常,再回退 PyFloat_AsDouble

3.3 try_complex_special_method:complex 协议与弃用警告

__complex__ 调用逻辑集中在静态函数 try_complex_special_methodObjects/complexobject.c),有两点值得扩展开发者注意:

  • 返回值必须是精确类型 complex。若返回严格子类实例,CPython 会发出 DeprecationWarning(对应 Issue #29894):“__complex__() must return a complex, not ... The ability to return an instance of a strict subclass of complex is deprecated...”;
  • __complex__ 抛出异常,异常会原样向上传播。

3.4 PyComplex_AsCComplex

Py_complex PyComplex_AsCComplex(PyObject *op);

返回 op 对应的 Py_complex 值。转换优先级:

  1. op 是复数对象(含子类型)时直接取 Py_complex 值;
  2. 否则优先调用 __complex__
  3. 若无 __complex__,回退 __float__
  4. __float__ 也未定义,再回退 __index__(文档 versionchanged 3.8:支持 __index__)。

失败时返回 real-1.0Py_complex 并设置异常,调用方同样应通过 PyErr_Occurred() 检查。实现见 Objects/complexobject.c

3.5 测试用例揭示的行为边界

Lib/test/test_capi/test_complex.py 对上述规则做了系统性验证,可作为你自写扩展时的行为参照:

  • 直接取值:realasdouble(1+2j) == 1.0realasdouble(42) == 42.0imagasdouble(4.25) == 0.0test_complex.py);
  • __complex__ 对象:realasdouble(Complex()) == 4.25imagasdouble(Complex()) == 0.5,且 BadComplex__complex__ 返回非复数)触发 TypeErrorBadComplex2(返回 complex 子类)触发 DeprecationWarning
  • 完全无协议的对象:realasdouble(object()) 抛出 TypeError
  • 注意测试注释 # CRASHES realasdouble(NULL)——与 PyComplex_Check 相同,这些函数不接受 NULL。

4. 复数作为 C 结构体:Py_c* 算术函数族(3.15 起软弃用)

文档专设 “Complex Numbers as C Structures” 一节,说明 API 提供一组直接基于 Py_complex 表示的算术函数,且这些函数按值接受和返回结构体。同时文档明确警告:

这些函数自 Python 3.15 起属于 soft deprecated(软弃用)。新代码不应使用它们做复数运算:要么使用 Number Protocol API 操作 Python 对象,要么直接使用原生复数类型(如 C 的 double complex)。

函数声明位于 Include/cpython/complexobject.h,实现全部在 Objects/complexobject.c 的前 400 行中。完整清单与语义如下:

函数 功能 特殊行为
_Py_c_sum(left, right) 两复数之和
_Py_c_diff(left, right) 两复数之差
_Py_c_neg(num) 复数取负
_Py_c_prod(left, right) 两复数之积
_Py_c_quot(dividend, divisor) 两复数之商 divisor 为零时返回零并设置 errno = EDOM
_Py_c_pow(num, exp) numexp 次幂 num 为零且 exp 不是正实数时返回零并设置 errno = EDOM;溢出时设置 errno = ERANGE
_Py_c_abs(num) 复数绝对值 溢出时设置 errno = ERANGE

以上所有函数均自 3.15 起弃用(文档逐一标注 deprecated 3.15)。

错误约定的关键点:这些函数不设置 Python 异常,而是通过 C 的 errno 报告域错误与溢出,且返回结构体中没有“失败标志”,所以调用方必须在调用前将 errno 清零、调用后检查 errno——CPython 自身的复数二元运算正是这样做的(见第 5 节的 COMPLEX_BINOP 宏)。

替代建议

  • 需要操作 Python 层对象(保留完整语义与异常体系):使用 Number Protocol,例如 PyNumber_AddPyNumber_Multiply 等;
  • 需要在 C 层做高性能复数运算:直接使用 double complex(C99 复数类型)与 cadd/cmul/cdiv/cpow 等 C 标准库函数。

5. 源码纵深:CPython 内部如何使用这组函数

虽然 _Py_c_* 对扩展作者软弃用,但它们仍是 CPython 内部实现 complex 类型算术的核心,理解其实现有助于解释 Python 层的数值行为。

5.1 混合运算的分派:COMPLEX_BINOP 宏

complex 类型的 +-*/ 都由 COMPLEX_BINOP(NAME, FUNC) 宏统一生成(Objects/complexobject.c)。宏实现了文档注释中描述的“混合模式”规则(参考 C11 Annex G.5.1/G.5.2):

  • 两边都是复数 → 调用 _Py_c_##FUNC(a, b)(复-复路径);
  • 左边是复数、右边是实数(int/float)→ 走 _Py_cr_##FUNC(a, b)(复-实路径);
  • 左边是实数、右边是复数 → 走 _Py_rc_##FUNC(a.real, b)(实-复路径)。

实-复、复-实的辅助函数(_Py_cr_sum_Py_rc_diff_Py_cr_quot 等)在 Include/internal/pycore_complexobject.h 中声明,实现同样在 [Objects/complexobject.c](https://gitcode.com/GitHub_Trending/cp/cpython/blob/486b000c6c19c555f03b481f735f4dec498f0f67/Objects/complexobject.c?utm_source=gitcode_repo_files#L40-L78, L248-L304)。

错误到异常的映射也集中在这里:errno == EDOM 时抛 ZeroDivisionError("division by zero");最终结果统一经 PyComplex_FromCComplex 包装返回。

5.2 _Py_c_quot:避免假溢出的分支除算法

复数除法是最容易出错的运算。_Py_c_quotObjects/complexobject.c)刻意避开了教科书式的“共轭乘积”公式(源码注释指出其“grossly prone to spurious overflow and underflow”),采用的是 Smith 算法的分支形式:先比较 |b.real||b.imag|,用绝对值较大者作为归一化因子:

if (abs_breal >= abs_bimag) {
    /* divide tops and bottom by b.real */
    if (abs_breal == 0.0) {
        errno = EDOM;
        r.real = r.imag = 0.0;
    } else {
        const double ratio = b.imag / b.real;
        const double denom = b.real + b.imag * ratio;
        r.real = (a.real + a.imag * ratio) / denom;
        r.imag = (a.imag - a.real * ratio) / denom;
    }
}

除零时按文档约定置 errno = EDOM 并返回 0+0j;若分母含 NaN 则结果直接为 NaN;另外还包含一段“从 nan+nanj 恢复无穷/零”的修复逻辑(对应 C11 Annex G.5.2 的 _Cdivd 语义)。

5.3 _Py_c_prod、_Py_c_pow 与 _Py_c_abs 的特殊值处理

  • 乘法Objects/complexobject.c):采用 ac - bd, ad + bc 展开,并在结果为 nan+nanj 时尝试按 C11 Annex G.5.1 的 _Cmultd 语义恢复无穷值(例如 inf * (something with nan) 情形);
  • 幂运算Objects/complexobject.c):基于 r^θ 的极坐标公式(hypot + atan2 + exp);exp == 0 时直接返回 1+0j;底数为零而指数非正实数时置 EDOM。Python 层 complex_powObjects/complexobject.c)在此基础上还有两个细节:小整数指数(|exp| <= 100 且虚部为 0)走更快的“平方-乘”快速路径 c_powiEDOM 映射为 ZeroDivisionError("zero to a negative or complex power")ERANGE 映射为 OverflowError("complex exponentiation")
  • 绝对值Objects/complexobject.c):按 C99 规则处理特殊值——任一分量为无穷则结果为无穷(即使另一分量是 NaN);否则用 hypot 计算,溢出(结果非有限)时置 errno = ERANGE,Python 层 complex_abs 会将其转为 OverflowError("absolute value too large")

5.4 测试用例对 errno 语义的验证

Lib/test/test_capi/test_complex.py 通过 _testcapi 模块直接调用这些 C 函数,并断言 errno 返回值,例如:

def test_py_c_quot(self):
    _py_c_quot = _testcapi._py_c_quot
    self.assertEqual(_py_c_quot(1, 1j), (-1j, 0))
    ...
    self.assertEqual(_py_c_quot(1, 0j)[1], errno.EDOM)   # 除零 → EDOM

def test_py_c_pow(self):
    _py_c_pow = _testcapi._py_c_pow
    self.assertEqual(_py_c_pow(0j, -1)[1], errno.EDOM)    # 0 的负数次幂 → EDOM
    max_num = DBL_MAX + 1j
    self.assertEqual(_py_c_pow(max_num, max_num),
                     (complex(INF, INF), errno.ERANGE))    # 溢出 → ERANGE

def test_py_c_abs(self):
    _py_c_abs = _testcapi._py_c_abs
    self.assertEqual(_py_c_abs(complex(*[DBL_MAX]*2))[1], errno.ERANGE)

这些测试覆盖了 NaN 传播(_py_c_quot(NAN, 1j) 两分量均为 NaN 但 errno 为 0)、无穷恢复(_py_cr_prod(complex('inf+1j'), INF))等边界,是验证自定义复数运算扩展行为的理想范本。相关 C 端包装见 Modules/_testcapi/complex.c

6. 实践速查与注意事项

  1. 访问实部/虚部:新代码用 PyComplex_AsCComplex(op) 一次取得 {real, imag};只需单分量时可用 PyComplex_RealAsDouble / PyComplex_ImagAsDouble。不要用 PyComplexObject.cval(3.15 弃用,3.20 移除)。
  2. 错误判定:所有“返回 -1 系”读路径(RealAsDouble/ImagAsDouble 返回 -1.0AsCComplexreal == -1.0)都必须配合 PyErr_Occurred() 判断,因为 -1 可以是合法数值。
  3. __complex__ 协议(3.13+):这三个读路径都会优先调用对象的 __complex__;要求它返回精确 complex 类型,返回子类会触发 DeprecationWarning
  4. NULL 参数PyComplex_CheckPyComplex_CheckExactRealAsDoubleImagAsDoubleAsCComplex 均不接受 NULL(测试文件多处标注 CRASHES)。
  5. 复数运算的选型:新扩展中避免 _Py_c_* 函数族(3.15 起软弃用)——Python 对象层面用 Number Protocol API,纯 C 数值层面用 double complex
  6. 引用语义PyComplex_FromDoubles / PyComplex_FromCComplex 返回新引用,由调用者负责 Py_DECREF;对象内部可能命中 freelist 缓存,不影响引用计数语义。

7. 相关源码与文档索引

内容 路径
C API 文档(本文主体) Doc/c-api/complex.rst
公共头文件(Limited API 可见部分) Include/complexobject.h
完整头文件(Py_complex、PyComplexObject、Py_c* 声明) Include/cpython/complexobject.h
内部辅助运算声明 Include/internal/pycore_complexobject.h
complex 类型完整实现 Objects/complexobject.c
C API 行为测试 Lib/test/test_capi/test_complex.py
测试辅助类型(Complex/BadComplex 等) Lib/test/test_capi/test_getargs.py
C 端测试包装 Modules/_testcapi/complex.c
3.20 待移除 API 清单(含 cval) Doc/deprecations/c-api-pending-removal-in-3.20.rst
登录后查看全文
热门项目推荐
相关项目推荐