CPython 抽象对象层(Abstract Objects Layer)详解:类型无关的 C API 设计与源码级剖析
CPython C 扩展开发中最常用的一类 API 是“抽象对象层”函数:它们不关心传入对象的具体类型,而是面向 list、dict、int、自定义对象等“宽类”统一操作,例如等价于 Python 表达式的 getattr、+、in、iter。本文以官方文档 Abstract Objects Layer 为骨架,完整梳理其七个子协议(对象、调用、数值、序列、映射、迭代器、缓冲区)的 API 语义,并结合 Objects/abstract.c 与 Include/abstract.h 的实现源码,深入讲解二目运算分派、NotImplemented 协议、下标查找回退链和迭代器耗尽判定等底层机制,帮助你在编写 C 扩展时既能正确调用这些函数,也能理解其错误约定与性能快路径。
一、章节定位:什么是抽象对象层
Doc/c-api/abstract.rst 开篇给出了三条关键约定,也是理解整个章节的前提:
- 类型无关性:本章节的函数作用于“任意 Python 对象”,或作用于某一类宽泛的对象集合(例如所有数值类型、所有序列类型)。
- 失败即抛异常:当函数被用于不适用的类型上时,会抛出 Python 异常(如
TypeError),而不是返回一个 C 层面的错误码。 - 初始化完整性要求:不能把这些函数用在“未正确初始化”的对象上——文档举例:一个通过
PyList_New创建但元素尚未设置为非NULL的 list 对象,此时调用抽象层函数是未定义行为。
这个章节在文档树中由一个 toctree 组织成七个子章节,各自对应 Include/abstract.h 中的一个协议区块:
| 子协议 | 文档 | 头文件区块(源码注释) |
|---|---|---|
| 对象协议 Object Protocol | object.rst | /* === Object Protocol === */ |
| 调用协议 Call Protocol | call.rst | PyObject_Call* 系列声明 |
| 数值协议 Number Protocol | number.rst | /* === Number Protocol === */ |
| 序列协议 Sequence Protocol | sequence.rst | /* === Sequence protocol === */ |
| 映射协议 Mapping Protocol | mapping.rst | /* === Mapping protocol === */ |
| 迭代器协议 Iterator Protocol | iter.rst | /* ==== Iterators ==== */ |
| 缓冲区协议 Buffer Protocol | buffer.rst | Include/pybuffer.h 单独提供 |
从源码结构看,前六个协议的公共实现集中在 Objects/abstract.c(约 2900 行),而对象协议中一部分最底层的函数(PyObject_RichCompare、PyObject_Hash、PyObject_IsTrue 等)实现在 Objects/object.c 中。
二、对象协议(Object Protocol):跨类型操作的基石
对象协议是抽象层中覆盖面最广的部分,API 定义见 Doc/c-api/object.rst。以下按功能分组梳理。
2.1 常量获取:Py_GetConstant 与 Py_GetConstantBorrowed
Python 3.13 引入 Py_GetConstant(unsigned int constant_id),用于获取常量的强引用。文档中的常量表如下(数值 ID 仅供无法使用宏标识符的项目参考):
| 常量标识符 | 数值 | 返回对象 |
|---|---|---|
Py_CONSTANT_NONE |
0 |
None |
Py_CONSTANT_FALSE |
1 |
False |
Py_CONSTANT_TRUE |
2 |
True |
Py_CONSTANT_ELLIPSIS |
3 |
Ellipsis |
Py_CONSTANT_NOT_IMPLEMENTED |
4 |
NotImplemented |
Py_CONSTANT_ZERO |
5 |
0 |
Py_CONSTANT_ONE |
6 |
1 |
Py_CONSTANT_EMPTY_STR |
7 |
'' |
Py_CONSTANT_EMPTY_BYTES |
8 |
b'' |
Py_CONSTANT_EMPTY_TUPLE |
9 |
() |
文档明确:CPython 中这些常量全部是 immortal(永生对象),因此 Py_GetConstant 每次返回的都是同一强引用。3.13 同时提供 Py_GetConstantBorrowed 返回借用引用,但官方标注它主要为向后兼容保留,新代码推荐使用 Py_GetConstant。
2.2 属性协议:HasAttr / GetAttr / SetAttr / DelAttr 家族
属性访问是 C 扩展与 Python 对象交互最高频的操作。文档给出了四组变体,语义差异需要精确掌握:
PyObject_GetAttr(o, attr_name):等价于o.attr_name,成功返回新强引用,失败返回NULL并置异常。PyObject_GetOptionalAttr(obj, attr_name, &result)(3.13 新增):属性不存在时不抛AttributeError——返回1并置*result为新强引用;返回0且*result为NULL(AttributeError被静默);返回-1表示其他错误。PyObject_SetAttr/PyObject_SetAttrString:等价于o.attr_name = v。注意文档在 3.15 中的变更说明:不得在异常已设置时传入NULL的v——此时属性会被删除,这种组合通常源于遗漏了NULL检查。v为NULL表示删除属性,但该行为已弃用,官方建议改用PyObject_DelAttr。PyObject_HasAttrWithError/PyObject_HasAttrStringWithError(3.13 新增):等价于hasattr(o, name)但会传播异常(返回-1)。相比之下,PyObject_HasAttr永远成功,其内部调用__getattr__/__getattribute__产生的异常不会传播,而是交给sys.unraisablehook——文档明确要求需要正确错误处理时改用WithError版本或PyObject_GetOptionalAttr。PyObject_DelAttr/PyObject_DelAttrString:等价于del o.attr。
一个值得注意的实现细节在 Include/abstract.h:在 limited C API 3.12 及更早版本中,PyObject_DelAttr/PyObject_DelAttrString 被宏定义为 PyObject_SetAttr(o, name, NULL),3.13 起才成为独立函数。此外 PyObject_SetAttrString 的文档还给出了性能建议:属性名应通过静态字符串控制数量;编译期未知的名字应先用 PyUnicode_FromString 构造后调用 PyObject_SetAttr。
通用属性槽:PyObject_GenericGetAttr / PyObject_GenericSetAttr 是可直接放进 PyTypeObject 的 tp_getattro/tp_setattro 槽的通用实现。从 Doc/c-api/object.rst 的语义描述看,getter 会在 MRO 各类型字典中查找描述符并在对象 __dict__ 中查找属性,数据描述符优先于实例属性;setter 则只查找数据描述符,优先于实例字典写入。PyObject_GenericGetDict/PyObject_GenericSetDict(3.3)分别提供 __dict__ 描述符的通用 getter/setter,其中 setter 不允许删除字典。
2.3 表示、比较与真值:Repr/Str/Hash/IsTrue
PyObject_Repr/PyObject_Str/PyObject_ASCII:分别等价于repr(o)、str(o)、ascii(o);传NULL时返回'<NULL>'。PyObject_Str同时是print的底层调用。PyObject_Bytes:等价于bytes(o),但有一个差异——当o是整数时bytes(o)会返回零初始化 bytes 对象,而PyObject_Bytes会抛TypeError。PyObject_RichCompare(o1, o2, opid):opid取Py_LT/Py_LE/Py_EQ/Py_NE/Py_GT/Py_GE,实现位于 Objects/object.c。PyObject_RichCompareBool额外返回-1/0/1三值,且有身份快捷路径:o1与o2是同一对象时,Py_EQ恒返回1、Py_NE恒返回0。PyObject_Hash:等价于hash(o),返回类型自 3.2 起为Py_hash_t(与Py_ssize_t同宽的有符号整数),失败返回-1;实现见 Objects/object.c。PyObject_HashNotImplemented可存入tp_hash槽,让类型显式声明自己不可哈希。PyObject_IsTrue/PyObject_Not:分别等价于not not o和not o,实现见 Objects/object.c。
2.4 类型检查与下标、目录
PyObject_Type:返回type(o)的新强引用;文档指出,若只需要类型指针而不需要引用,应使用Py_TYPE()宏。PyObject_TypeCheck(o, type):判断o是否为type的实例或子类实例,两个参数都不得为NULL。PyObject_IsSubclass/PyObject_IsInstance:语义与issubclass/isinstance完全一致——cls为元组时逐项检查;cls定义__subclasscheck__/__instancecheck__时走 PEP 3119 协议;实例可通过__class__属性、类可通过__bases__属性覆盖默认判断。PyObject_Size/PyObject_Length:等价于len(o)。当对象同时提供序列与映射协议时返回序列长度(PyObject_Length只是 DLL 兼容别名)。PyObject_LengthHint(o, default)(3.4):等价于operator.length_hint,实现(Objects/abstract.c)的顺序是__len__→__length_hint__→ 默认值,且会对__length_hint__的结果做“必须返回非负整数”的校验。- 下标:
PyObject_GetItem/PyObject_SetItem(不偷取v的引用)/PyObject_DelItem/PyObject_DelItemString,分别对应o[key]、o[key] = v、del o[key]。 PyObject_Dir(o):等价于dir(o);传NULL时等价于无参dir(),若无活动帧则返回NULL但不置异常。
2.5 3.12–3.15 新增的对象层 API
近年版本在对象协议中加入了多个面向类型系统与内存管理的新函数:
PyObject_GetTypeData/PyType_GetTypeDataSize/PyObject_GetItemData(3.12):分别用于访问负basicsize类型保留的子类数据区、Py_TPFLAGS_ITEMS_AT_END类型的每元素数据区,文档反复强调“Python 不检查前置条件”,调用方必须自行保证类型确实以负basicsize方式创建。PyObject_VisitManagedDict/PyObject_ClearManagedDict(3.13):仅在类型设置了Py_TPFLAGS_MANAGED_DICT时可在 traverse/clear 函数中调用。PyUnstable_Object_EnableDeferredRefcount(3.14):在无 GIL 构建中为对象启用延迟引用计数——对象只由追踪式 GC 回收,从而避免多线程下的引用计数竞争;在 GIL 构建中为空操作。PyUnstable_TryIncRef/PyUnstable_EnableTryIncRef(3.14):原子地“若引用计数非零则加一”,官方给出了用它实现类WeakValueDictionary的 weakmap 的完整 C 示例(Doc/c-api/object.rst)。PyUnstable_Object_IsUniquelyReferenced(3.14):由于 3.14 起解释器在将对象加载到操作数栈时可能借用引用,Py_REFCNT == 1不再能保证唯一引用,该函数在 free-threaded 构建下还会检查对象是否仅被当前线程使用。PyUnstable_SetImmortal(3.15):将对象标记为 immortal,单向不可逆;immortal 对象不参与引用计数、永不被 GC 回收,若被 GC 追踪则会被解除追踪。PyObject_Dump(op)(3.15):调试用对象转储,输出到stderr,设计上要在内存损坏后仍尽量可调用(按“最不可能崩溃的字段优先”顺序输出),并带有“对象内存是否已释放”的启发式检测。
三、调用协议(Call Protocol)
Include/abstract.h 集中声明了调用家族的 C API,与 Doc/c-api/call.rst 对应。从声明顺序可以归纳出一套递进关系:
PyObject *PyObject_CallNoArgs(PyObject *func); /* 3.9+,无参调用 */
PyObject *PyObject_Call(PyObject *callable,
PyObject *args, PyObject *kwargs); /* callable(*args, **kwargs) */
PyObject *PyObject_CallObject(PyObject *callable, PyObject *args);
PyObject *PyObject_CallFunction(PyObject *callable,
const char *format, ...); /* mkvalue 格式串 */
PyObject *PyObject_CallMethod(PyObject *obj, const char *name,
const char *format, ...);
PyObject *PyObject_CallFunctionObjArgs(PyObject *callable, ...); /* NULL 结尾的 PyObject* 列表 */
PyObject *PyObject_CallMethodObjArgs(PyObject *obj, PyObject *name, ...);
PyObject *PyObject_Vectorcall(PyObject *callable,
PyObject *const *args, size_t nargsf,
PyObject *kwnames); /* PEP 590 */
PyObject *PyObject_VectorcallMethod(PyObject *name, PyObject *const *args,
size_t nargsf, PyObject *kwnames);
版本门槛在头文件中以 Py_LIMITED_API 条件编译体现:PyObject_CallNoArgs 需要 3.9+(abstract.h),PyObject_Vectorcall/PyObject_VectorcallMethod 需要 3.12+ 且依赖 PY_VECTORCALL_ARGUMENTS_OFFSET 高位标记位(abstract.h)。PEP 590 风格的 vectorcall 直接接收裸 PyObject*const* 数组与关键字名元组,省去了构造 args 元组的开销,是 C 扩展中最高效的调用入口。
四、数值协议(Number Protocol):二目运算的分派机制
数值协议(Doc/c-api/number.rst、Include/abstract.h)暴露了完整的二元/一元/原地运算函数:PyNumber_Add…PyNumber_Or、PyNumber_InPlace* 系列、PyNumber_Power(三目)、以及转换函数 PyNumber_Index/PyNumber_AsSsize_t/PyNumber_Long/PyNumber_Float/PyNumber_ToBase。
4.1 binary_op1:NotImplemented 驱动的槽位分派
所有二目运算共用 Objects/abstract.c 中的 binary_op1,源码注释写明分派顺序:
依次尝试:w.op(v,w)[仅当 w 是 v 的真子类时优先] → v.op(v,w) → w.op(v,w)
核心逻辑是:
- 从
Py_TYPE(v)->tp_as_number取 v 类型的nb_*槽位(宏NB_BINOP按offsetof从结构体中取函数指针); - 仅当 w 与 v 不同类型时才取 w 类型的对应槽位,且若两槽位指向同一函数则置空以避免重复调用;
- 每个槽位调用后检查结果是否为
Py_NotImplemented——是则Py_DECREF并尝试下一个槽位(源码注释/* can't do it */); - 全部失败则返回
PyNotImplemented,由外层 binary_op 统一转换为unsupported operand type(s) for <op>: '<t1>' and '<t2>'的TypeError(binop_type_error)。
这正是 Python 层面 __add__ 返回 NotImplemented 以让反向操作数接管运算的 C 层实现,也是 Doc/c-api/object.rst 中 Py_NotImplemented 单例与 Py_RETURN_NOTIMPLEMENTED 宏存在的意义。
4.2 数值协议与序列协议的交叉回退
两个特例展示了抽象层跨协议协作的设计:
- PyNumber_Add:
nb_add槽位全部返回NotImplemented后,会回退到左操作数的序列槽sq_concat——所以list + list在 C 层最终走的是序列拼接而非数值加法。 - PyNumber_Multiply:
nb_multiply失败后回退到sq_repeat,且左右两侧都尝试;sequence_repeat辅助函数(abstract.c)会先把次数参数通过PyNumber_AsSsize_t转成Py_ssize_t,非整数直接报can't multiply sequence by non-int of type ...。
三目运算 PyNumber_Power(v, w, z) 则走 ternary_op,分派顺序为 v.op(v,w,z) → w.op(v,w,z) → z.op(v,w,z);当 z 为 None 时错误消息只显示两个操作数类型。
4.3 原地运算的“无槽即回退”约定
Objects/abstract.c 的注释明确约定:原地运算符(+=、*= 等)若左操作数没有对应的 nb_inplace_* 槽,则按普通运算处理——返回结果可以是原对象,也可以是新对象。binary_iop1 的实现正是“先试 inplace 槽,失败(NotImplemented)则降级为普通 binary_op1”。
4.4 PyNumber_Index 与 PyNumber_AsSsize_t
PyIndex_Check 只检查类型是否填充了 nb_index 槽;PyNumber_Index 将其转换为 Python int;PyNumber_AsSsize_t(o, exc) 在此基础上进一步转成 C 的 Py_ssize_t,转换溢出时若 exc 非 NULL 则抛出该异常,为 NULL 则清错并钳位。这是 C 扩展把 Python 侧索引/长度参数安全转成 C 整数的标准途径,序列与映射协议的下标函数内部均依赖它。
五、序列协议与映射协议
5.1 序列协议
API 与 Doc/c-api/sequence.rst 对应,包括 PySequence_Check、PySequence_Size、PySequence_Concat/Repeat、PySequence_GetItem/SetItem/DelItem、PySequence_GetSlice/SetSlice/DelSlice、PySequence_Tuple/List、PySequence_Count、PySequence_Contains(源码级兼容别名 PySequence_In)、PySequence_Index,以及原地版本 PySequence_InPlaceConcat/InPlaceRepeat(“尽可能原地,返回结果可能是 o1 本身”)。
PySequence_Fast(o, m) 是 C 扩展热路径上最重要的函数:它把对象统一转成 list/tuple(已是 tuple/list 则原样返回),配合 PySequence_Fast_GET_ITEM/PySequence_Fast_GET_SIZE 宏实现 O(1) 元素访问;不支持迭代的对象会以 m 作为消息文本抛出 TypeError。
5.2 映射协议
映射协议(Doc/c-api/mapping.rst)提供 PyMapping_Check、PyMapping_Size、PyMapping_HasKey(String)(永远成功,吞掉异常)与 3.13 新增的 PyMapping_HasKeyWithError(String)(失败返回 -1)、PyMapping_Keys/Values/Items、PyMapping_GetItemString、PyMapping_SetItemString,以及 3.13 的 PyMapping_GetOptionalItem(String)(不抛 KeyError 的三值变体,limited API 门槛 3.13,见 abstract.h)。PyMapping_DelItem(String) 在头文件中直接是 PyObject_DelItem(String) 的宏别名。
5.3 实现中的协议优先级:序列优先于映射
从 Objects/abstract.c 的 PyObject_Size 看,长度查找顺序是:先序列槽 sq_length,再映射(PyMapping_Size)——这就是文档“两者皆有则返回序列长度”约定的落点。
而下标查找 PyObject_GetItem 恰好相反,回退链为:
tp_as_mapping->mp_subscript(即__getitem__语义);tp_as_sequence->sq_item:仅当 key 是索引对象(_PyIndex_Check)时,经PyNumber_AsSsize_t转成Py_ssize_t再走PySequence_GetItem,否则报sequence index must be integer;- 特例:对
type对象,int[...]走Py_GenericAlias,其他类走__class_getitem__,没有则报type 'X' is not subscriptable; - 其余对象报
'X' object is not subscriptable。
性能上还有一个 dict 快路径:PyMapping_GetOptionalItem 对精确的 dict 类型直接调用 PyDict_GetItemRef,绕过通用 PyObject_GetItem 的槽位调用与异常清理。
六、迭代器协议(Iterator Protocol)
Doc/c-api/iter.rst 定义了迭代器协议的全部函数:PyIter_Check、PyAIter_Check(3.10)、PyIter_NextItem(3.14)、PyIter_Next、PySendResult 枚举与 PyIter_Send(3.10)。
6.1 获取迭代器:PyObject_GetIter 的回退链
Objects/abstract.c 实现了清晰的三级策略:
- 类型填充了
tp_iter槽则直接调用,并校验返回值确实是迭代器(否则报X.__iter__() must return an iterator, not Y); - 无
tp_iter但通过PySequence_Check(即有__getitem__序列语义)则构造PySeqIter_New序列迭代器——这就是老式__getitem__迭代协议的 C 层实现; - 都不是则报
'X' object is not iterable。
异步版本 PyObject_GetAIter(abstract.c)只检查 tp_as_async->am_aiter,无序列回退。
6.2 “耗尽”与“出错”的判定:iternext 静态函数
这是迭代器协议最精妙的部分。iternext 的判定规则:
tp_iternext返回非NULL→ 正常取到值,返回1;- 返回
NULL且无异常 → 正常耗尽,返回0; - 返回
NULL且异常是StopIteration→ 清掉异常,视为耗尽,返回0(源码注释:a StopIteration exception may or may not be set,即容忍实现里“忘了清 StopIteration”的迭代器); - 返回
NULL且异常是其他类型 → 错误,返回-1。
3.14 新增的 PyIter_NextItem 将这一三值语义公开为稳定 API(1 = 取到值、0 = 耗尽、-1 = 出错),文档明确要求新代码优先使用它替代 PyIter_Next;PyIter_Next 仅以“耗尽/出错都返回 NULL”的方式暴露,调用方必须自行用 PyErr_Occurred() 区分。PyIter_Check(abstract.c)则以 tp_iternext 非空且不等于哨兵 _PyObject_NextNotImplemented 为判据。
6.3 向迭代器/生成器发值:PyIter_Send
PyIter_Send 返回 PYGEN_NEXT(yield 出值)/PYGEN_RETURN(生成器返回,值经 StopIteration 提取到 presult)/PYGEN_ERROR。实现上有三条路径:类型提供 am_send 槽则直接调用;arg 为 None 且对象是迭代器则直接走 tp_iternext;否则调用 send(arg) 方法。这为 C 扩展中的 async for 与生成器驱动提供了与解释器一致的行为。
七、缓冲区协议与整体使用约定
缓冲区协议(buffer.rst)声明位于 Include/pybuffer.h,提供 PyObject_GetBuffer/PyBuffer_FillInfo/PyBuffer_IsContiguous 等函数,让 C 代码以 Py_buffer 结构零拷贝访问支持 buffer 协议的对象(如 bytes、array、numpy 数组)。它与其余六个协议的关系是:抽象对象层负责“是什么/有什么操作”,缓冲区协议负责“原始内存如何暴露”。
综合全章节,使用抽象层 API 需遵守的三条工程约定(均见 Doc/c-api/abstract.rst 与头文件声明):
- 错误约定:对象/调用/数值类函数失败时返回
NULL且异常已置位;int返回值函数失败返回-1;“永远成功”的函数(PyCallable_Check、PyIter_Check、PyMapping_HasKey、PySequence_Check等)在头文件注释中逐一标明。 - 引用计数:返回
PyObject*的查询类函数一律返回新强引用,由调用方负责Py_DECREF;PyObject_SetItem等写入函数不偷取引用。 - 版本门槛:头文件用
Py_LIMITED_API条件编译控制符号可见性(例如 abstract.h 中PyObject_DelAttrString在 limited 3.12 及之前只是宏、abstract.h 中PyIter_NextItem要求 3.14+),跨版本扩展应按这些门槛选择 API。
八、验证材料与延伸阅读
- 协议语义的系统性测试在 Lib/test/test_abstract.py(覆盖对象协议各变体)与 Lib/test/test_buffer.py(缓冲区协议)等测试文件中,可作为 API 行为的活字典;
- 数值/序列/映射/迭代各协议的完整函数签名与错误语义,分别见 number.rst、sequence.rst、mapping.rst、iter.rst;
- CPython 内部专用的扩展声明(如
_PyMapping_GetOptionalItem2、PyUnstable_*的非 stable 细节)位于 Include/cpython/abstract.h,仅在非 limited 构建下由 Include/abstract.h 自动包含; - 对象协议在解释器侧的对应实现(
PyObject_RichCompare、PyObject_Hash、PyObject_IsTrue)见 Objects/object.c。
按这套“文档语义 + 源码分派链”的阅读方式,你可以对任何一个抽象层函数快速回答三个问题:它对哪些类型生效、失败时抛什么异常、以及引用该由谁释放——这正是编写健壮 C 扩展所需的全部判断依据。
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 StartedRust0627
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