CPython C 扩展参数解析与返回值构建:PyArg_Parse 系列与 Py_BuildValue 格式串全解 CPython C 扩展参数解析与返回值构建PyArg_Parse 系列与 Py_BuildValue 格式串全解【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython本文基于 CPython 官方文档 Doc/c-api/arg.rst 展开系统讲解 C 扩展开发中“参数解析”PyArg_Parse*家族与“返回值构建”Py_BuildValue两大核心机制。读完本文你将理解格式串format string的完整语法、字符串/缓冲区的三种内存语义Py_buffer借用、分配缓冲、裸指针借用、所有数字与对象格式单元、|、$、:、;等特殊控制字符的用法并能结合 Python/getargs.c 与 Python/modsupport.c 的源码印证底层实现写出可复制、可运行的 C 扩展代码。一、总体模型格式串驱动的参数解析在编写 C 扩展函数时Python 调用层会把参数打包传递给你传统METH_VARARGS调用约定传一个PyObject *args元组METH_VARARGS | METH_KEYWORDS额外传关键字字典METH_FASTCALL则传一个参数指针数组加计数。PyArg_Parse*系列函数的职责就是把这堆PyObject*按你指定的“格式串”逐个转换、存进 C 局部变量。文档明确了三个入口都使用同一套格式串语法PyArg_ParseTuple—— 解析只有位置参数的函数参数args元组PyArg_ParseTupleAndKeywords—— 解析位置 关键字参数PyArg_Parse—— 解析单个位置参数配合METH_O调用约定。格式串由零个或多个“格式单元”format unit组成。每个格式单元描述一个 Python 对象通常是一个单独字符也可以是括号括起来的格式单元序列。除少数例外一个非嵌套括号的格式单元对应一个 C 侧的“地址参数”——即你传入的局部变量的地址。文档约定引号形式是格式单元圆括号内是匹配的 Python 对象类型方括号内是应传入地址的 C 变量类型。从源码结构看这三个函数最终都汇聚到 Python/getargs.c 中的vgetargs1_impl变参被展平为va_list再由convertsimple逐字符分发处理见 Python/getargs.c#L712。PyArg_Parse走vgetargs1(args, format, va, FLAG_COMPAT)分支Python/getargs.c#L78-L87PyArg_ParseTuple走vgetargs1(..., 0)Python/getargs.c#L103-L112。转换成功/失败的语义转换成功要求arg对象与格式完全匹配且格式串必须被完整耗尽。成功时函数返回非零true失败时返回 0 并抛出相应异常。当某个格式单元转换失败时该单元及其后所有格式单元对应的 C 变量都保持原值不变——你无需手动清理已写入的变量。二、字符串与缓冲区三种内存语义文档将“字符串/缓冲区转 C”归为三类这是 C 扩展中最容易踩内存坑的地方必须分清各自的释放责任y*、s*等填充Py_buffer的格式它们会锁定lock底层缓冲区使你在Py_BEGIN_ALLOW_THREADS代码块中使用时也不会遇到可变数据被扩容或销毁的风险。作为代价你必须在处理完毕后包括任何提前退出路径调用PyBuffer_Release释放。es、es#、et、et#由PyArg_ParseTuple负责分配结果缓冲区。你必须在处理完毕后调用PyMem_Free释放。“借用”缓冲borrowed buffer其余格式如s、s#、y、y#接收str或只读 bytes-like 对象直接给出const char *裸指针。该缓冲区由对应 Python 对象管理生命周期与对象一致你不需要释放任何内存。第 3 类“借用”有两层安全约束文档说得很明确对象的PyBufferProcs.bf_releasebuffer字段必须为NULL。这排除了常见的可变对象如bytearray也排除了某些只读对象例如指向bytes的memoryview除此之外CPython不检查输入对象是否真的不可变例如它是否会响应可写缓冲请求或另一个线程是否可能修改数据。注意在 Python 3.12 及更早版本中若要使用所有#变体格式s#、y#等必须在#include Python.h之前定义宏PY_SSIZE_T_CLEANPython 3.13 及之后不再需要。2.1 字符串/缓冲区格式单元速查格式单元接受的 Python 类型C 变量类型说明sstrconst char *转换为 NUL 结尾的 UTF-8 C 字符串含内嵌 NUL 时抛ValueError编码失败抛UnicodeError。不接受 bytes-like 对象s*str或 bytes-likePy_buffer接受 Unicode 与 bytes-like可含内嵌 NUL需PyBuffer_Releases#str、只读 bytes-likeconst char *、Py_ssize_t借用缓冲指针 长度两个变量可含内嵌 NULzstr或Noneconst char *同s但None时指针置NULLz*str、bytes-like 或NonePy_buffer同s*None时buf成员为NULLz#str、只读 bytes-like 或Noneconst char *、Py_ssize_t同s#None时指针为NULLy只读 bytes-likeconst char *不接受 Unicode含内嵌 NUL 抛ValueErrory*bytes-likePy_buffer官方推荐接收二进制数据的方式y#只读 bytes-likeconst char *、Py_ssize_t同s#但仅限 bytes-likeSbytesPyBytesObject *或PyObject *严格类型检查不做转换非 bytes 抛TypeErrorYbytearrayPyByteArrayObject *或PyObject *同上严格bytearrayUstrPyObject *严格 Unicode 检查不做转换w*可读写 bytes-likePy_buffer接受实现可读写缓冲接口的对象需PyBuffer_ReleaseS、Y、U的共同点是只验证类型、不做任何转换因此 C 变量直接拿到对应对象指针C 侧也可直接声明为PyObject*。关于s的补充来自文档的 notes不接受 bytes-like 对象。如果你要接收文件系统路径并转成 C 字符串更合适的是用O格式配合PyUnicode_FSConverter作为 converter3.5 之前的版本对内嵌 NUL 抛TypeError3.5 起改为ValueError。2.2es/et/es#/et#显式指定编码esstr→const char *encoding, char **buffer把 Unicode 编码成字符缓冲只支持不含内嵌 NUL 的编码结果。它需要两个 C 参数第一个仅作输入指向编码名的 NUL 结尾 C 字符串或NULL表示用utf-8指定了 Python 不认识的编码会抛异常第二个必须是char **解析后指向编码结果的缓冲区。PyArg_ParseTuple会分配恰好需要的空间、拷贝数据并调整指针——调用方负责用PyMem_Free释放。etstr/bytes/bytearray→ 同上与es相同但字节串对象不经重新编码直接透传实现上假定该字节串对象已经使用了你传入的参数编码。es#str→const char *encoding, char **buffer, Py_ssize_t *buffer_length与es的区别是允许输入含 NUL 字符。第三个参数是指向整数的指针被设置为输出缓冲的字节数。它有两种工作模式若*buffer初始为NULL函数分配所需缓冲区并拷贝调用方须PyMem_Free若*buffer指向已分配的缓冲区直接使用该内存并把*buffer_length的初始值解释为缓冲区容量拷贝并 NUL 结尾容量不足时抛ValueError。两种模式下*buffer_length最终都设置为编码数据的长度不含结尾 NUL 字节。et#与es#相同只是字节串对象直接透传不重编码。从源码实现印证这些“需要清理”的分配Py_buffer与char **在 Python/getargs.c 中通过cleanup_ptr内部调PyMem_FreePython/getargs.c#L202-L209与cleanup_buffer内部调PyBuffer_ReleasePython/getargs.c#L211-L219登记到 freelist一旦解析中途失败cleanreturnPython/getargs.c#L235-L252会自动执行已登记的清理函数避免异常路径下的内存泄漏——这正是文档所说“或任何提前退出情形”背后有兜底的原因。此外3.12 起u、u#、Z、Z#已被移除因为它们依赖遗留的Py_UNICODE*表示。2.3 借用引用的通用规则文档专门强调传给调用方的任何 Python 对象引用都是借用引用borrowed reference不要释放它们即不要减少引用计数。同样额外传入这些函数的参数必须是“类型由格式串决定的变量”的地址用来存放输入元组中的值只有少数格式单元上文的es、es#等把额外参数当输入用此时必须与文档对应条目匹配。三、数字格式整数、字符与浮点数字格式把 Python 数字或单字符表示为 C 数字。要求int、float、complex的格式也可以调用对象对应的__index__、__float__或__complex__方法完成转换。范围语义上有符号整数格式值超出 C 类型范围抛OverflowError无符号整数格式接收域太小时最高位静默截断当值大于 C 类型最大值或小于同尺寸有符号类型的最小值时发出DeprecationWarning。完整对照表引号格式单元 / 接受类型 / C 变量类型格式单元Python 类型C 变量类型说明bintunsigned char非负整数转无符号 tiny intBintunsigned char不做溢出检查hintshort intHintunsigned short intiintintIintunsigned intlintlong intkintunsigned long3.14 起可用__index__Lintlong longKintunsigned long long3.14 起可用__index__nintPy_ssize_t首选的“平台指针尺寸整数”c长度为 1 的bytes或bytearraychar3.3 起允许bytearrayC长度为 1 的strintffloatfloatdfloatdoubleDcomplexPy_complex注意 3.15 起对无符号格式B、H、I、k、K当值超出范围时会发出DeprecationWarning文档标记为 deprecated 行为预警。源码印证Python/getargs.c 的convertsimple中b用PyLong_AsLong后手动检查 0与 UCHAR_MAX抛OverflowError而B走PyLong_AsNativeBytes且对超宽值调PyErr_WarnEx(PyExc_DeprecationWarning, integer value out of range, 1)Python/getargs.c#L712-L780与文档描述逐字对应。四、其他对象格式O、O!、O、p与嵌套元组4.1O与O!Oobject →PyObject *把 Python 对象原样存入 C 对象指针不创建新强引用引用计数不增加存入的指针非NULL。O!object →typeobject,PyObject *与O类似但取两个 C 参数第一个是 Python 类型对象的地址第二个是存放对象指针的PyObject*变量地址类型不符抛TypeError。4.2O自定义转换器O通过converter函数把 Python 对象转成任意类型的 C 变量。它取两个参数converter 函数本身以及目标 C 变量地址转成void *。converter 的调用协议是status converter(object, address);object是待转换对象address是传给PyArg_Parse*的void*。成功返回 1失败返回 0且 converter 应抛出异常并保持address内容不变。官方示例 converterPyUnicode_FSConverter与PyUnicode_FSDecoder。Py_CLEANUP_SUPPORTED机制如果 converter 返回Py_CLEANUP_SUPPORTED标记当参数解析最终失败时它可能被第二次调用以释放已分配的内存——第二次调用时object参数为NULLaddress与第一次相同。4.3p与(items)pbool→int3.3 加入对传入值做真值测试predicate结果为真置 1、假置 0。接受任何合法的 Python 值真值语义见文档 truth 一节。(items)sequence → 对应matching-items对象必须是 Python 序列str、bytes、bytearray除外长度必须等于items中格式单元的数量C 参数须与items的单元一一对应且允许嵌套。两条安全约束若items内含存借用缓冲的单元s、s#、z、z#、y、y#或借用引用的单元S、Y、U、O、O!则该对象必须是 tuple3.14 起str与bytearray不再被接受为序列3.14 同时把“含借用单元时用非 tuple 序列”标记为 deprecated。items中O的 converter 不得存储借用缓冲或借用引用。4.4 特殊控制字符|、$、:、;这些字符不能出现在嵌套括号内|其后的参数变为可选。可选参数对应的 C 变量必须预先初始化为默认值——当调用方没有提供该参数时PyArg_ParseTuple不会触碰这些变量。例如OO|OO对应 Python 签名f(a, b, cNone, dNone)。$仅PyArg_ParseTupleAndKeywords3.3 加入其后的参数变为 keyword-only。若$之前出现过|则它们是可选的否则是必需的|不能出现在$之后。例如O|O$O对应f(a, bNone, *, cNone)OO$OO对应f(a, b, *, c, d)。:格式单元列表到此结束冒号后的字符串用作错误信息中的函数名即PyArg_ParseTuple抛出异常的“关联值”。实践中强烈建议总是加上例如i:my_function这样报错信息会写成my_function() argument must be ...。;格式单元列表到此结束分号后的字符串整体替代默认错误信息。:与;互斥。五、API 函数族一览5.1 解析函数int PyArg_ParseTuple(PyObject *args, const char *format, ...); int PyArg_VaParse(PyObject *args, const char *format, va_list vargs); int PyArg_ParseTupleAndKeywords(PyObject *args, PyObject *kw, const char *format, char * const *keywords, ...); int PyArg_VaParseTupleAndKeywords(PyObject *args, PyObject *kw, const char *format, char * const *keywords, va_list vargs); int PyArg_ValidateKeywordArguments(PyObject *); int PyArg_Parse(PyObject *args, const char *format, ...); int PyArg_ParseArray(PyObject *const *args, Py_ssize_t nargs, const char *format, ...); // 3.15 int PyArg_ParseArrayAndKeywords(PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames, const char *format, const char * const *kwlist, ...); // 3.15PyArg_VaParse/PyArg_VaParseTupleAndKeywords与变参版本功能相同只是接受va_list。PyArg_ParseTupleAndKeywords的keywords参数是NULL 结尾的关键字参数名数组NUL 结尾的 ASCII/UTF-8 C 字符串空字符串名表示 positional-only 参数3.6 加入。版本变化要点3.13 起keywords参数类型在 C 中是char * const *、C 中是const char * const *不再是char **并支持非 ASCII 关键字参数名可用PY_CXX_CONST宏覆盖该前缀C 默认为空、C 默认为const在包含Python.h前定义即可覆盖3.13 加入。PyArg_ValidateKeywordArguments3.2 加入确保关键字字典的键都是字符串。只有在你不用PyArg_ParseTupleAndKeywords它已内置该检查时才需要。PyArg_Parse解析单个位置参数面向METH_O调用约定。文档给出的官方示例// Function using METH_O calling convention static PyObject* my_function(PyObject *module, PyObject *arg) { int value; if (!PyArg_Parse(arg, i:my_function, value)) { return NULL; } // ... use value ... }PyArg_ParseArray/PyArg_ParseArrayAndKeywords均为 3.15 加入分别解析METH_FASTCALL与METH_FASTCALL | METH_KEYWORDS约定下的数组参数PyObject *const *argsnargs以及关键字参数kwnameskwlist。从源码看它们都收敛到vgetargs1_impl/vgetargskeywords_implPython/getargs.c#L139-L170与元组版本共享同一套格式单元语义。5.2PyArg_UnpackTuple不用格式串的简单取参int PyArg_UnpackTuple(PyObject *args, const char *name, Py_ssize_t min, Py_ssize_t max, ...);这是一种“简单取参”形式不使用格式串指定类型。使用它的函数应在函数/方法表中声明为METH_VARARGSargs必须是元组长度至少min、至多max两者可以相等。额外参数各是一个指向PyObject*变量的指针将被填入args中对应的值——注意是借用引用。未提供的可选参数对应的变量不会被写入应由调用方预初始化。args不是元组或元素数量不对时返回 false 并设置异常。文档引用自_weakref辅助模块的源码示例static PyObject * weakref_ref(PyObject *self, PyObject *args) { PyObject *object; PyObject *callback NULL; PyObject *result NULL; if (PyArg_UnpackTuple(args, ref, 1, 2, object, callback)) { result PyWeakref_NewRef(object, callback); } return result; }文档指出这个调用与下面的PyArg_ParseTuple调用完全等价PyArg_ParseTuple(args, O|O:ref, object, callback)源码印证PyArg_UnpackTuple实现于 Python/getargs.c#L2898失败分支会设置PyArg_UnpackTuple() argument list is not a tuple错误与文档描述一致。六、构建返回值Py_BuildValue与Py_VaBuildValue6.1 基本契约PyObject* Py_BuildValue(const char *format, ...); PyObject* Py_VaBuildValue(const char *format, va_list vargs);Py_BuildValue用与PyArg_Parse*相似的格式串 一组值创建新的 Python 值出错返回NULL且异常已置位。Py_VaBuildValue与之相同只是接受va_list。从源码看Py_BuildValue直接转发到va_build_valuePython/modsupport.c#L496-L503非法格式字符会触发bad format char passed to Py_BuildValue的 SystemError 路径Python/modsupport.c#L487。三条易被忽略的契约不总是返回 tuple只有格式串含两个及以上格式单元时才构建 tuple空格式串返回None恰好一个单元时返回该单元描述的对象本身。要强制得到 0 元或 1 元 tuple请给格式串加括号如(i)。缓冲区是拷贝而非引用以s、s#等格式提供的内存缓冲其数据会被拷贝Py_BuildValue创建的对象从不引用调用方的缓冲。换言之若你malloc后把内存传给Py_BuildValuePy_BuildValue返回后由你负责free该内存。格式串中的空格、制表符、冒号和逗号被忽略s#这类单元内部除外可用来提高长格式串的可读性。6.2 构建格式单元对照表格式单元返回的 Python 类型C 参数类型说明sstr或Noneconst char *NUL 结尾 C 串按utf-8解码指针为NULL时得Nones#str或Noneconst char *、Py_ssize_t串 长度NULL时忽略长度得Noneybytesconst char *C 串转bytesNULL得Noney#bytesconst char *、Py_ssize_tC 串 长度NULL得Nonez/z#str或None同s/s#与s/s#相同u/u#strconst wchar_t * 长度宽字符缓冲UTF-16 或 UCS-4转 UnicodeNULL得NoneU/U#str或None同s/s#与s/s#相同iintintbintcharhintshort intlintlong intBintunsigned charHintunsigned short intIintunsigned intkintunsigned longLintlong longKintunsigned long longnintPy_ssize_tpboolint必须传 int变参不做自动类型收缩其他类型可用(x) ? 1 : 0或!!x转换3.14 加入c长度 1 的byteschar表示一个字节C长度 1 的strint表示一个字符d/ffloatdouble/floatDcomplexPy_complex *注意传结构体地址OobjectPyObject *原样传递但创建新强引用引用计数 1传入NULL时假定上游出错并已置异常——Py_BuildValue返回NULL但不抛新异常若尚无异常则置SystemErrorSobjectPyObject *同ONobjectPyObject *同O但不创建新强引用适合对象由参数列表中的构造器调用创建的情形如Py_BuildValue(N, obj)把所有权交给返回值Oobjectconverter,anything通过 converter 把anything应与void*兼容转为新 Python 对象或NULL(items)tuple对应 C 值构建等长元组[items]list对应 C 值构建等长列表{items}dict成对 C 值每连续两个值构成一对键值格式串本身有语法错误时置SystemError并返回NULL。七、实战要点小结对应文档结论选对函数只有位置参数用PyArg_ParseTuple位置 关键字用PyArg_ParseTupleAndKeywords记住|/$语义与空名 positional-onlyMETH_O单参数用PyArg_ParseMETH_FASTCALL用 3.15 的PyArg_ParseArray/PyArg_ParseArrayAndKeywords不想引入类型转换就用PyArg_UnpackTuple。格式串尾随:函数名几乎总是值得写它直接决定用户看到什么报错需要完全自定义错误文案时用;message替代二者互斥。可选参数先赋默认值|之后的变量在调用方省略时不会被写入。分清三种内存语义Py_buffer系s*/y*/w*/z*用PyBuffer_Release收尾es/es#/et/et#用PyMem_Free收尾借用指针s/s#/y/y#/z系不释放但生命周期依赖源对象解析器自身的失败路径会自动走 freelist 兜底清理见 Python/getargs.c#L200-L252。Py_BuildValue的O/N选择已有引用、想移交所有权用N普通对象引用、需要 1 强引用用ONULL参数的语义是“上游已出错”的哨兵。更多扩展函数与方法的上下文示例可参阅 Doc/extending/index.rst格式串的公共声明位于 Include/modsupport.h如PyArg_ParseTuple的原型声明实现主体在 Python/getargs.c解析与 Python/modsupport.c构建。适用前提以上版本行为3.13 的keywords类型变化、3.14 的k/K支持__index__、3.15 的PyArg_ParseArray*与无符号溢出DeprecationWarning等均以当前仓库CPython 主干对应 3.15 开发版本文档与源码为准移植到 3.13/3.14 及以下运行时请核对对应版本的差异。【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

最新新闻

Spec Kit 项目演进史:从 SDD 工具包到编码 Agent 可扩展框架的完整脉络

Spec Kit 项目演进史:从 SDD 工具包到编码 Agent 可扩展框架的完整脉络

Spec Kit 项目演进史:从 SDD 工具包到编码 Agent 可扩展框架的完整脉络 【免费下载链接】spec-kit 💫 Toolkit to help you get started with Spec-Driven Development 项目地址: https://gitcode.com/GitHub_Trending/sp/spec-kit 本文基于 Spec…

2026/9/7 3:57:56
我的世界服务器永久不删档,搭建稳定长期运行生存服

我的世界服务器永久不删档,搭建稳定长期运行生存服

这次我们来看一个“一辈子”的MC服务器需求:标题是“我们要开一个一辈子的MC服务器!【我的世界MC 生存服务器 26.1.2 永久不删档】”。这类需求在朋友联机、小型社区服里非常常见——不是开一个两天就删的测试服,而是想长期保留同一个生存档&…

2026/9/7 3:57:56
言语脑机接口通信度量:从WER到ITR的Python实现指南

言语脑机接口通信度量:从WER到ITR的Python实现指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/7 3:57:56
大模型应用落地实战:RAG、微调与部署的技术栈全解析

大模型应用落地实战:RAG、微调与部署的技术栈全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/7 3:57:56
3万棵树的渲染性能优化:从瓶颈定位到系统性方案

3万棵树的渲染性能优化:从瓶颈定位到系统性方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/7 3:57:56
ES8311 Linux音频驱动开发实战:从ASoC框架到设备树配置与调音踩坑

ES8311 Linux音频驱动开发实战:从ASoC框架到设备树配置与调音踩坑

简介:面向嵌入式音频开发者,该驱动资源围绕低功耗音频CODEC芯片ES8311,聚焦I2S音频数据传输与I2C寄存器控制的典型场景,适用于Linux或RTOS环境下的驱动移植、功能验证与问题排查,可帮助解决音频子系统中常见的初始化失…

2026/9/7 3:52:55