ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

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

2026/9/7 20:06:33 拓冰建站 浏览量
CPython C API 复数对象深度指南:Py_complex 表示、PyComplex 函数族与 _Py_c_* 运算函数 CPython C API 复数对象深度指南Py_complex 表示、PyComplex 函数族与Py_c* 运算函数【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython本文基于 CPython 官方文档 Doc/c-api/complex.rstComplex 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 中通过两个类型描述复数PyComplexObjectPython 对象与Py_complexC 层导出格式。1.1 PyComplexObjectPyObject 的复数子类PyComplexObject是PyObject的子类型表示一个 Python 复数对象。其结构定义在 Include/cpython/complexobject.htypedef 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.htypedef 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(12j)与check(ComplexSubclass(12j))均为真而checkexact(ComplexSubclass(12j))为假对int、float、普通object均返回假。测试中还明确标注check(NULL)会崩溃即这两个函数不接受 NULL 参数。2. 创建复数对象PyComplex_FromDoubles 与 PyComplex_FromCComplex2.1 PyComplex_FromDoublesPyObject* PyComplex_FromDoubles(double real, double imag);从real和imag两个double创建一个新的PyComplexObject出错时返回NULL并设置异常。从源码看Objects/complexobject.c它只是把两个 double 打包成Py_complex后委托给PyComplex_FromCComplex因此两条路径共享同一套分配逻辑。2.2 PyComplex_FromCComplex 与对象缓存freelistPyObject* PyComplex_FromCComplex(Py_complex v);从 CPy_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.py 中complex_fromccomplex(12j) 1.02.0j、complex_fromdoubles(1.0, 2.0) 1.02.0j。3. 读取复数值PyComplex_RealAsDouble / PyComplex_ImagAsDouble / PyComplex_AsCComplex这三个函数是 Python 对象到 C 数值的读路径。它们的关键共性是参数不必是精确的complex类型API 会按一套“数值协议”尽力转换且失败时有明确的可检测约定。3.1 PyComplex_RealAsDoubledouble PyComplex_RealAsDouble(PyObject *op);返回op的实部Cdouble。转换规则op是复数对象含子类型时直接取实部否则若op定义了__complex__方法先调用它把op转换为复数对象Python 3.13 起引入此行为见文档versionchanged 3.13若没有定义__complex__则回退调用PyFloat_AsDouble并返回其结果。失败时返回-1.0并设置异常因此必须调用PyErr_Occurred()检查错误因为-1.0本身也可能是合法的实部值。3.2 PyComplex_ImagAsDoubledouble 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_methodcomplex协议与弃用警告__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_AsCComplexPy_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(12j) 1.0、realasdouble(42) 42.0、imagasdouble(4.25) 0.0test_complex.py__complex__对象realasdouble(Complex()) 4.25、imagasdouble(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)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 complexC99 复数类型与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://link.gitcode.com/i/724c7b466999e44200ac6fe5d1677101#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并返回00j若分母含 NaN 则结果直接为 NaN另外还包含一段“从nannanj恢复无穷/零”的修复逻辑对应 C11 Annex G.5.2 的_Cdivd语义。5.3 _Py_c_prod、_Py_c_pow 与 _Py_c_abs 的特殊值处理乘法Objects/complexobject.c采用ac - bd, ad bc展开并在结果为nannanj时尝试按 C11 Annex G.5.1 的_Cmultd语义恢复无穷值例如inf * (something with nan)情形幂运算Objects/complexobject.c基于r^θ的极坐标公式hypotatan2expexp 0时直接返回10j底数为零而指数非正实数时置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 ERANGEPython 层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(inf1j), INF)等边界是验证自定义复数运算扩展行为的理想范本。相关 C 端包装见 Modules/_testcapi/complex.c。6. 实践速查与注意事项访问实部/虚部新代码用PyComplex_AsCComplex(op)一次取得{real, imag}只需单分量时可用PyComplex_RealAsDouble/PyComplex_ImagAsDouble。不要用PyComplexObject.cval3.15 弃用3.20 移除。错误判定所有“返回 -1 系”读路径RealAsDouble/ImagAsDouble返回-1.0AsCComplex的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.hcomplex类型完整实现Objects/complexobject.cC API 行为测试Lib/test/test_capi/test_complex.py测试辅助类型Complex/BadComplex 等Lib/test/test_capi/test_getargs.pyC 端测试包装Modules/_testcapi/complex.c3.20 待移除 API 清单含 cvalDoc/deprecations/c-api-pending-removal-in-3.20.rst【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考