CPython C API 复数对象深度指南:Py_complex 表示、PyComplex 函数族与 _Py_c_* 运算函数
本文基于 CPython 官方文档 Doc/c-api/complex.rst(Complex Number Objects 一章)展开,面向 C 扩展开发者,系统讲解如何在 C 层面创建、检查、转换 CPython 的 complex 对象,以及底层 Py_complex 表示与一组复数算术函数的工作方式。读完本文,你将掌握 PyComplex_FromDoubles、PyComplex_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 的复数子类
PyComplexObject 是 PyObject 的子类型,表示一个 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_AsCComplex和PyComplex_FromCComplex完成 Pythoncomplex对象与 CPy_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_new、tp_hash、tp_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)) 为假;对 int、float、普通 object 均返回假。测试中还明确标注 check(NULL) 会崩溃,即这两个函数不接受 NULL 参数。
2. 创建复数对象:PyComplex_FromDoubles 与 PyComplex_FromCComplex
2.1 PyComplex_FromDoubles
PyObject* PyComplex_FromDoubles(double real, double imag);
从 real 和 imag 两个 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_dealloc(Objects/complexobject.c)中:只有精确类型的 complex(PyComplex_CheckExact 为真)才会放回 freelist,子类型实例走 tp_free 常规释放。这一机制解释了为什么复数对象的高频创建/销毁(如数值计算扩展)开销较低。
测试印证:Lib/test/test_capi/test_complex.py 中 complex_fromccomplex(1+2j) == 1.0+2.0j、complex_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)。转换规则:
op是复数对象(含子类型)时,直接取实部;- 否则若
op定义了__complex__方法,先调用它把op转换为复数对象(Python 3.13 起引入此行为,见文档versionchanged 3.13); - 若没有定义
__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_method(Objects/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 值。转换优先级:
op是复数对象(含子类型)时直接取Py_complex值;- 否则优先调用
__complex__; - 若无
__complex__,回退__float__; - 若
__float__也未定义,再回退__index__(文档versionchanged 3.8:支持__index__)。
失败时返回 real 为 -1.0 的 Py_complex 并设置异常,调用方同样应通过 PyErr_Occurred() 检查。实现见 Objects/complexobject.c。
3.5 测试用例揭示的行为边界
Lib/test/test_capi/test_complex.py 对上述规则做了系统性验证,可作为你自写扩展时的行为参照:
- 直接取值:
realasdouble(1+2j) == 1.0、realasdouble(42) == 42.0、imagasdouble(4.25) == 0.0(test_complex.py); __complex__对象:realasdouble(Complex()) == 4.25、imagasdouble(Complex()) == 0.5,且BadComplex(__complex__返回非复数)触发TypeError,BadComplex2(返回 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) |
num 的 exp 次幂 |
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_Add、PyNumber_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_quot(Objects/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_pow(Objects/complexobject.c)在此基础上还有两个细节:小整数指数(|exp| <= 100且虚部为 0)走更快的“平方-乘”快速路径c_powi;EDOM映射为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. 实践速查与注意事项
- 访问实部/虚部:新代码用
PyComplex_AsCComplex(op)一次取得{real, imag};只需单分量时可用PyComplex_RealAsDouble/PyComplex_ImagAsDouble。不要用PyComplexObject.cval(3.15 弃用,3.20 移除)。 - 错误判定:所有“返回 -1 系”读路径(
RealAsDouble/ImagAsDouble返回-1.0,AsCComplex的real == -1.0)都必须配合PyErr_Occurred()判断,因为 -1 可以是合法数值。 __complex__协议(3.13+):这三个读路径都会优先调用对象的__complex__;要求它返回精确complex类型,返回子类会触发DeprecationWarning。- NULL 参数:
PyComplex_Check、PyComplex_CheckExact、RealAsDouble、ImagAsDouble、AsCComplex均不接受 NULL(测试文件多处标注 CRASHES)。 - 复数运算的选型:新扩展中避免
_Py_c_*函数族(3.15 起软弃用)——Python 对象层面用 Number Protocol API,纯 C 数值层面用double complex。 - 引用语义:
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 |
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00