
CPython 扩展类型开发详解从零到循环垃圾回收的 PyTypeObject 完全指南【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython本文基于 CPython 官方教程 newtypes_tutorial.rst 整理展开系统讲解如何在 C 扩展模块中定义可被 Python 代码操作的新类型从最小可运行的Custom类型到添加数据成员与方法、实现细粒度属性控制、支持循环垃圾回收cyclic GC直至派生内置list类型的SubList。读完之后你将掌握PyTypeObject各tp_槽位的职责、引用计数的安全写法并能独立完成一个可安装、可测试的扩展类型模块。一、基础原理PyObject、类型对象与类型方法CPython 运行时把所有 Python 对象都看作类型为PyObject*的变量PyObject*是所有 Python 对象的“基类型”。PyObject结构体本身只包含对象的引用计数reference count和一个指向“类型对象”type object的指针——一切行为都由类型对象决定解释器在查属性、调用方法、执行乘法等操作时具体调用哪个 C 函数全部由类型对象中的函数指针称为 “type methods”决定。因此定义一个新的扩展类型本质就是创建一个新的PyTypeObject。在源码中可以看到基础定义object.h 第 65 行定义了每个对象结构体必须开头的头部宏/* PyObject_HEAD defines the initial segment of every PyObject. */ #define PyObject_HEAD PyObject ob_base;PyObject_HEAD定义了一个名为ob_base、类型为PyObject的字段其中保存类型指针和引用计数分别可用宏Py_TYPE与Py_REFCNT访问。使用宏而非直接访问字段是为了抽象掉底层布局差异并让 debug 构建如开启引用追踪的构建可以插入额外字段。一个最小但完整、在模块custom中定义类型Custom的模块如下完整源码见 custom.c#define PY_SSIZE_T_CLEAN #include Python.h typedef struct { PyObject_HEAD /* Type-specific fields go here. */ } CustomObject; static PyTypeObject CustomType { .ob_base PyVarObject_HEAD_INIT(NULL, 0) .tp_name custom.Custom, .tp_doc PyDoc_STR(Custom objects), .tp_basicsize sizeof(CustomObject), .tp_itemsize 0, .tp_flags Py_TPFLAGS_DEFAULT, .tp_new PyType_GenericNew, }; static int custom_module_exec(PyObject *m) { if (PyType_Ready(CustomType) 0) { return -1; } if (PyModule_AddObjectRef(m, Custom, (PyObject *) CustomType) 0) { return -1; } return 0; } static PyModuleDef_Slot custom_module_slots[] { {Py_mod_exec, custom_module_exec}, // Just use this while using static types {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED}, {0, NULL} }; static PyModuleDef custom_module { .m_base PyModuleDef_HEAD_INIT, .m_name custom, .m_doc Example module that creates an extension type., .m_size 0, .m_slots custom_module_slots, }; PyMODINIT_FUNC PyInit_custom(void) { return PyModuleDef_Init(custom_module); }这个文件定义了三样东西Custom对象的内容CustomObject结构体每个Custom实例分配一份Custom类型的行为CustomType结构体定义一组标志和函数指针解释器在请求特定操作时检查它们custom模块的定义与执行PyInit_custom函数与custom_modulePyModuleDef结构以及负责初始化模块对象的custom_module_exec函数。1.1 对象结构体PyObject_HEAD 是必须的开头typedef struct { PyObject_HEAD } CustomObject;注意宏PyObject_HEAD后面没有分号宏展开后自带分号多加一个会引来编译器抱怨。对象通常还要在标准头部之外保存自己的数据例如标准浮点数的定义就是PyObject_HEAD加一个double ob_fval成员。1.2 类型对象逐字段拆解static PyTypeObject CustomType { .ob_base PyVarObject_HEAD_INIT(NULL, 0) .tp_name custom.Custom, .tp_doc PyDoc_STR(Custom objects), .tp_basicsize sizeof(CustomObject), .tp_itemsize 0, .tp_flags Py_TPFLAGS_DEFAULT, .tp_new PyType_GenericNew, };官方推荐用 C99 风格指定初始化器designated initializers这样既不必列出所有不关心的PyTypeObject字段也不用关心字段声明顺序。object.h中PyTypeObject的完整定义字段远比上面多未指定的字段会被 C 编译器补零通常的惯例是不需要就不显式写。逐字段说明.ob_base PyVarObject_HEAD_INIT(NULL, 0)初始化ob_base字段的必备样板代码。.tp_name custom.Custom类型的名字会出现在对象的默认文本表示和部分错误信息中例如 custom.Custom() Traceback (most recent call last): File stdin, line 1, in module TypeError: can only concatenate str (not custom.Custom) to str注意这是一个点分名同时包含模块名和模块内的类型名这里是模块custom中的类型Custom所以类型名设为custom.Custom。使用真实的点分导入路径对让类型与pydoc和pickle模块兼容非常重要。.tp_basicsize sizeof(CustomObject).tp_itemsize 0让 Python 知道创建新Custom实例时要分配多少内存。tp_itemsize仅用于可变大小的对象其余情况应为 0。官方文档在此处特别提示了一个多继承陷阱如果你的类型希望可从 Python 子类化且tp_basicsize与基类相同可能遇到多重继承问题——Python 子类必须把你的类型列在__bases__第一位否则无法调用你的__new__。避免办法是让tp_basicsize大于基类大多数情况天然成立因为基类通常是object或你在基类上追加了数据成员。.tp_flags Py_TPFLAGS_DEFAULT所有类型都应包含这个常量它启用了至少到 Python 3.3 为止定义的全部成员需要更多成员时OR 上对应标志即可。.tp_doc PyDoc_STR(Custom objects)类型的文档字符串。.tp_new PyType_GenericNew要允许创建对象必须提供tp_new处理函数它等价于 Python 方法__new__但必须显式指定。这里直接使用 API 提供的默认实现PyType_GenericNew。1.3 模块初始化PyType_Ready 与 PyModule_AddObjectRefcustom_module_exec中还有两段关键代码if (PyType_Ready(CustomType) 0) { return -1; }PyType_Ready初始化Custom类型把大量成员填充为默认值包括最初设为NULL的ob_type字段。if (PyModule_AddObjectRef(m, Custom, (PyObject *) CustomType) 0) { return -1; }把类型加入模块字典这样就能通过调用类来创建实例 import custom mycustom custom.Custom()1.4 构建与安装把上面的代码存为custom.c配套文件放在 pyproject.toml[build-system] requires [setuptools] build-backend setuptools.build_meta [project] name custom version 1以及 setup.py初始版本只有一个扩展from setuptools import Extension, setup setup(ext_modules[Extension(custom, [custom.c])])在 shell 中执行$ python -m pip install .即可生成并安装扩展文件如custom.so启动 Python 后就能import custom并随意摆弄Custom对象了。当然此时的Custom类型相当无趣没有数据、不执行任何操作甚至不能被子类化。二、为类型添加数据成员与方法这一节把基础例子扩展为带数据和方法的版本并让类型可作为基类使用。新模块custom2的完整源码见 custom2.c。2.1 对象结构first、last 与 numberCustom类型现在有三个数据属性first、last和number。前两者是保存名与姓的 Python 字符串number是 C 整型typedef struct { PyObject_HEAD PyObject *first; /* first name */ PyObject *last; /* last name */ int number; } CustomObject;2.2 析构函数tp_dealloc有了要管理的数据就必须更谨慎地处理对象分配与释放。至少要提供一个析构方法static void Custom_dealloc(PyObject *op) { CustomObject *self (CustomObject *) op; Py_XDECREF(self-first); Py_XDECREF(self-last); Py_TYPE(self)-tp_free(self); }并赋给tp_dealloc槽.tp_dealloc Custom_dealloc,该函数先释放两个 Python 属性上的引用。Py_XDECREF能正确处理参数为NULL的情况若tp_new中途失败就可能发生。然后调用对象类型的tp_free成员通过Py_TYPE(self)求得释放对象内存。注意对象的类型未必是CustomType——对象可能是子类实例。教程特别强调显式转型到CustomObject *是必需的因为tp_dealloc函数指针期望接收PyObject *参数。把函数挂到类型的tp_dealloc槽即声明了它只会用我们自己的CustomObject实例调用所以转型是安全的——这可以称为“C 语言里的面向对象多态”。旧代码中常见直接让Custom_dealloc接收CustomObject *self并在赋值槽时强转(destructor)的写法在 CPython 支持的所有架构上行为相同但按 C 标准属于未定义行为应避免。2.3 创建函数tp_new 与 tp_alloc为了让first和last初始化为空字符串提供tp_new实现static PyObject * Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { CustomObject *self; self (CustomObject *) type-tp_alloc(type, 0); if (self ! NULL) { self-first PyUnicode_FromString(); if (self-first NULL) { Py_DECREF(self); return NULL; } self-last PyUnicode_FromString(); if (self-last NULL) { Py_DECREF(self); return NULL; } self-number 0; } return (PyObject *) self; }并安装到.tp_new Custom_new,。tp_new负责创建而非初始化对象在 Python 中暴露为__new__。很多扩展类型直接复用PyType_GenericNew这里用tp_new把first/last初始化为非NULL默认值。tp_new接收被实例化的类型若是子类化不一定是CustomType和调用类型时传入的参数返回创建的实例。tp_new处理函数始终接受位置参数和关键字参数但常常忽略它们把参数处理留给初始化器C 中是tp_initPython 中是__init__。tp_new不应显式调用tp_init解释器会自己调用。tp_new实现中调用tp_alloc槽分配内存self (CustomObject *) type-tp_alloc(type, 0);内存分配可能失败所以必须对tp_alloc结果判空。教程补充两点我们没有自己填tp_alloc槽——PyType_Ready会从基类默认是object继承它。大多数类型都用默认分配策略。如果你写的是协作式tp_new会调用基类型的tp_new或__new__不能在运行时用 MRO 决定调用哪个方法必须静态确定要调用的类型直接调用其tp_new或经由type-tp_base-tp_new。否则同时继承自其他 Python 类子的 Python 子类可能无法正确创建实例会抛出TypeError。一个从源码结构可见的现代细节教程正文示例使用PyUnicode_FromString()创建空字符串而当前仓库的 custom2.c 第 27 行已改用常量池接口Py_GetConstant(Py_CONSTANT_EMPTY_STR)获取共享的空字符串常量语义相同但避免重复构造这也说明阅读文档时可与仓库中的实际源码对照确认最新写法。2.4 初始化函数tp_init 与引用计数的安全替换再定义一个接受参数的初始化函数为实例提供初始值static int Custom_init(PyObject *op, PyObject *args, PyObject *kwds) { CustomObject *self (CustomObject *) op; static char *kwlist[] {first, last, number, NULL}; PyObject *first NULL, *last NULL, *tmp; if (!PyArg_ParseTupleAndKeywords(args, kwds, |OOi, kwlist, first, last, self-number)) return -1; if (first) { tmp self-first; Py_INCREF(first); self-first first; Py_XDECREF(tmp); } if (last) { tmp self-last; Py_INCREF(last); self-last last; Py_XDECREF(tmp); } return 0; }并填充.tp_init Custom_init,。tp_init槽在 Python 中暴露为__init__用于在对象创建之后做初始化。初始化器始终接受位置参数和关键字参数成功返回0、出错返回-1。与tp_new不同tp_init没有任何保证一定会被调用例如pickle模块默认在反序列化时不调用__init__也可能被调用多次——任何人都能调用我们对象的__init__。因此给属性赋值必须格外小心。比如像这样写是有风险的if (first) { Py_XDECREF(self-first); Py_INCREF(first); self-first first; }我们的类型并没有限制first成员的类型它可以是任意对象。它的析构器可能触发会再次访问first的代码执行也可能在析构期间脱离线程状态detach thread state让其他线程的任意代码访问并修改我们的对象。为防这种情况我们几乎总是先把新值赋进去再递减旧值引用计数。仅有的例外是以下三种情况完全确定引用计数大于 1确定对象析构既不会脱离线程状态也不会回调我们类型的代码当对象确定是字符串或浮点数等基本类型时可以这样认为在tp_dealloc中递减一个不支持循环垃圾回收的类型的成员引用。顺带说明仓库中的 custom2.c 第 55 行把上述“先存后换”模式收敛成了Py_XSETREF(self-first, Py_NewRef(first));——这是 CPython 近年引入的新式引用管理宏一条语句完成“建新引用 原子替换 释放旧引用”语义与教程中的手写三步等价且更不易出错。2.5 数据属性PyMemberDef 与 tp_members要把实例变量暴露为属性最简单的方式是定义成员表static PyMemberDef Custom_members[] { {first, Py_T_OBJECT_EX, offsetof(CustomObject, first), 0, first name}, {last, Py_T_OBJECT_EX, offsetof(CustomObject, last), 0, last name}, {number, Py_T_INT, offsetof(CustomObject, number), 0, custom number}, {NULL} /* Sentinel */ };并放入tp_members槽.tp_members Custom_members,每个成员定义包含成员名、类型、偏移量、访问标志和文档字符串定义见 structmember.h。这种方式的缺点是无法限制可赋值到这些 Python 属性上的对象类型我们期望first/last是字符串但任意 Python 对象都能被赋值而且属性可以被del删除C 指针随之变为NULL。2.6 方法PyMethodDef 与 tp_methods定义单个方法Custom.name输出名与姓的拼接static PyObject * Custom_name(PyObject *op, PyObject *Py_UNUSED(dummy)) { CustomObject *self (CustomObject *) op; if (self-first NULL) { PyErr_SetString(PyExc_AttributeError, first); return NULL; } if (self-last NULL) { PyErr_SetString(PyExc_AttributeError, last); return NULL; } return PyUnicode_FromFormat(%S %S, self-first, self-last); }方法实现为一个 C 函数第一个参数是Custom或其子类实例——方法永远以实例作为第一参数。此处没有额外参数因此不需要接受参数元组或关键字字典。它等价于 Python 方法def name(self): return %s %s % (self.first, self.last)注意必须检查first/last是否为NULL因为它们可能被删除删除时 C 指针被置为NULL。更好的做法是禁止删除并把属性值限制为字符串——下一节解决。把方法放进方法定义数组static PyMethodDef Custom_methods[] { {name, Custom_name, METH_NOARGS, Return the name, combining the first and last name }, {NULL} /* Sentinel */ };使用METH_NOARGS标志表示方法除self外不接收参数然后赋给tp_methods槽.tp_methods Custom_methods,2.7 允许子类化最后让类型可用作基类。此前写的方法都不对被操作对象的类型做假设所以只需给类标志加上Py_TPFLAGS_BASETYPE.tp_flags Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,相应地把PyInit_custom改名为PyInit_custom2更新PyModuleDef中的模块名和PyTypeObject中的完整类名custom2.Custom。更新setup.py加入新模块后重新安装from setuptools import Extension, setup setup(ext_modules[ Extension(custom, [custom.c]), Extension(custom2, [custom2.c]), ])$ python -m pip install .仓库中的 setup.py 是最终完整版同时注册了全部五个示例扩展from setuptools import Extension, setup setup(ext_modules[ Extension(custom, [custom.c]), Extension(custom2, [custom2.c]), Extension(custom3, [custom3.c]), Extension(custom4, [custom4.c]), Extension(sublist, [sublist.c]), ])三、为数据属性提供更精细的控制上一节的版本中first和last可以被设为非字符串值甚至被删除。这一节完整源码见 custom3.c用自定义 getter/setter 确保这两个属性始终包含字符串。3.1 getter 与 setterfirst属性的存取函数static PyObject * Custom_getfirst(PyObject *op, void *closure) { CustomObject *self (CustomObject *) op; Py_INCREF(self-first); return self-first; } static int Custom_setfirst(PyObject *op, PyObject *value, void *closure) { CustomObject *self (CustomObject *) op; PyObject *tmp; if (value NULL) { PyErr_SetString(PyExc_TypeError, Cannot delete the first attribute); return -1; } if (!PyUnicode_Check(value)) { PyErr_SetString(PyExc_TypeError, The first attribute value must be a string); return -1; } tmp self-first; Py_INCREF(value); self-first value; Py_DECREF(tmp); return 0; }getter 收到Custom对象和一个 “closure”void 指针。这里闭包被忽略闭包支持的进阶用法是把定义期数据传给 getter/setter例如让一组 getter/setter 函数根据闭包中的数据决定操作哪个属性。setter 收到Custom对象、新值和闭包新值可能为NULL此时表示属性正在被删除。我们的 setter 在删除属性或新值不是字符串时抛出错误。3.2 PyGetSetDef 与 tp_getset创建PyGetSetDef结构体数组static PyGetSetDef Custom_getsetters[] { {first, Custom_getfirst, Custom_setfirst, first name, NULL}, {last, Custom_getlast, Custom_setlast, last name, NULL}, {NULL} /* Sentinel */ };并注册到tp_getset槽.tp_getset Custom_getsetters,PyGetSetDef结构的最后一项就是上面说的闭包此处不使用传NULL。同时移除这两个属性的成员定义static PyMemberDef Custom_members[] { {number, Py_T_INT, offsetof(CustomObject, number), 0, custom number}, {NULL} /* Sentinel */ };3.3 同步收紧 tp_init还要更新tp_init处理函数用格式码U仅接受str替代O只允许传字符串static int Custom_init(PyObject *op, PyObject *args, PyObject *kwds) { CustomObject *self (CustomObject *) op; static char *kwlist[] {first, last, number, NULL}; PyObject *first NULL, *last NULL, *tmp; if (!PyArg_ParseTupleAndKeywords(args, kwds, |UUi, kwlist, first, last, self-number)) return -1; if (first) { tmp self-first; Py_INCREF(first); self-first first; Py_DECREF(tmp); } if (last) { tmp self-last; Py_INCREF(last); self-last last; Py_DECREF(tmp); } return 0; }经过这些修改可以确信first和last成员永远不会是NULL于是绝大多数Py_XDECREF调用都可以换成Py_DECREF仓库的 custom3.c 第 55 行实际使用了等价的Py_SETREF宏完成替换。唯一不能改的地方是tp_dealloc那里仍存在tp_new中成员初始化失败导致指针为NULL的可能。教程脚注还解释了一个容易忽略的点即便把属性限制为字符串用户仍可传入str的子类实例。虽然析构普通字符串不会回调我们的对象但无法保证析构字符串子类实例时不会回调进来——所以“先赋值后释放”的谨慎写法依然保留。再按惯例重命名模块初始化函数与模块名并在setup.py中追加custom3的定义即可。四、支持循环垃圾回收Python 有循环垃圾回收器cyclic GC即使引用计数不为零也能识别无用对象这发生在对象参与引用环时。例如 l [] l.append(l) del l这个列表包含自身。删除后它仍持有自引用引用计数不会归零幸运的是循环垃圾回收器最终会判定它是垃圾并释放。在第二版Custom示例中first/last可存放任意类型对象在第二、三版中又允许子类化而子类可以添加任意属性。这两个原因都足以让Custom对象参与引用环 import custom3 class Derived(custom3.Custom): pass ... n Derived() n.some_attribute n要让参与引用环的Custom实例被正确检测并回收Custom类型需要再填两个槽并启用对应标志完整源码见 custom4.c。4.1 遍历函数 tp_traverse遍历函数让循环 GC 知道哪些子对象可能参与环static int Custom_traverse(PyObject *op, visitproc visit, void *arg) { CustomObject *self (CustomObject *) op; int vret; if (self-first) { vret visit(self-first, arg); if (vret ! 0) return vret; } if (self-last) { vret visit(self-last, arg); if (vret ! 0) return vret; } return 0; }对每个可能参与环的子对象都要调用传入的visit函数它接收子对象和传给遍历函数的额外参数arg返回非零值时必须原样返回。Python 提供Py_VISIT宏来自动完成visit调用大幅减少样板代码static int Custom_traverse(PyObject *op, visitproc visit, void *arg) { CustomObject *self (CustomObject *) op; Py_VISIT(self-first); Py_VISIT(self-last); return 0; }注意tp_traverse实现要想使用Py_VISIT其参数必须恰好命名为visit和arg。4.2 清空函数 tp_clear 与 Py_CLEAR需要提供清空可参与环的子对象的方法static int Custom_clear(PyObject *op) { CustomObject *self (CustomObject *) op; Py_CLEAR(self-first); Py_CLEAR(self-last); return 0; }注意这里使用了Py_CLEAR宏——它是清空任意类型数据属性并递减引用计数的推荐且安全的方式。如果在置空之前先调用Py_XDECREF属性的析构器可能回调再次读取该属性的代码引用环存在时尤其危险。可以这样模拟Py_CLEARPyObject *tmp; tmp self-first; self-first NULL; Py_XDECREF(tmp);但总是用Py_CLEAR删除属性要简单得多、出错概率低得多不要为了微优化牺牲健壮性。4.3 重写析构函数并启用 Py_TPFLAGS_HAVE_GC析构器在清空属性时可能执行任意代码这意味着 GC 可能在该函数内部被触发。由于 GC 假定对象引用计数非零必须在清空成员之前调用PyObject_GC_UnTrack把对象从 GC 中注销。重写后的析构函数static void Custom_dealloc(PyObject *op) { PyObject_GC_UnTrack(op); (void)Custom_clear(op); Py_TYPE(op)-tp_free(op); }最后给类标志加上Py_TPFLAGS_HAVE_GC.tp_flags Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,基本就齐了。如果写过自定义tp_alloc或tp_free处理函数还要为循环 GC 修改它们大多数扩展直接使用自动提供的版本即可。仓库中的 test.py 用 doctest 验证了 GC 行为构造custom4.Custom子类并人为制造s.cycle [s]的引用环后禁用 GC删除s后目标对象引用计数仍高于基线gc.collect()之后引用计数回落证明该扩展类型确实能被循环 GC 正确回收。五、派生其他类型以 list 为基类的 SubList可以创建从现有类型派生的新扩展类型。继承内置类型最容易因为扩展可以直接使用所需的PyTypeObject在多个扩展模块之间共享这些结构则相对困难。本例创建从内置list类型继承的SubList类型完整源码见 sublist.c。新类型与常规列表完全兼容但额外提供increment方法递增一个内部计数器 import sublist s sublist.SubList(range(3)) s.extend(s) print(len(s)) 6 print(s.increment()) 1 print(s.increment()) 2#define PY_SSIZE_T_CLEAN #include Python.h typedef struct { PyListObject list; int state; } SubListObject; static PyObject * SubList_increment(PyObject *op, PyObject *Py_UNUSED(dummy)) { SubListObject *self (SubListObject *) op; self-state; return PyLong_FromLong(self-state); } static PyMethodDef SubList_methods[] { {increment, SubList_increment, METH_NOARGS, PyDoc_STR(increment state counter)}, {NULL}, }; static int SubList_init(PyObject *op, PyObject *args, PyObject *kwds) { SubListObject *self (SubListObject *) op; if (PyList_Type.tp_init(op, args, kwds) 0) return -1; self-state 0; return 0; } static PyTypeObject SubListType { .ob_base PyVarObject_HEAD_INIT(NULL, 0) .tp_name sublist.SubList, .tp_doc PyDoc_STR(SubList objects), .tp_basicsize sizeof(SubListObject), .tp_itemsize 0, .tp_flags Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, .tp_init SubList_init, .tp_methods SubList_methods, };源码与前面Custom例子高度相似主要差异如下。5.1 基类结构体必须位于首位typedef struct { PyListObject list; int state; } SubListObject;派生类型对象的对象结构体中基类结构体必须放在第一个成员位置。基类结构体自身已经以PyObject_HEAD开头。这样当一个 Python 对象是SubList实例时其PyObject *指针可以安全地转型为PyListObject *和SubListObject *二者static int SubList_init(PyObject *op, PyObject *args, PyObject *kwds) { SubListObject *self (SubListObject *) op; if (PyList_Type.tp_init(op, args, kwds) 0) return -1; self-state 0; return 0; }上面演示了如何调用到基类类型的__init__方法。5.2 tp_base 必须在模块执行函数中赋值写带自定义tp_new和tp_dealloc的类型时这个模式很重要tp_new处理函数不应用自己的tp_alloc实际创建对象内存而应通过调用基类自己的tp_new让基类处理分配。PyTypeObject结构体支持tp_base槽指定类型的直接基类。由于跨平台编译问题不能直接把这个字段初始化为指向PyList_Type的引用应在Py_mod_exec函数中设置static int sublist_module_exec(PyObject *m) { SubListType.tp_base PyList_Type; if (PyType_Ready(SubListType) 0) { return -1; } if (PyModule_AddObjectRef(m, SubList, (PyObject *) SubListType) 0) { return -1; } return 0; }调用PyType_Ready之前必须填好tp_base槽。派生已有类型时不必用PyType_GenericNew填充tp_alloc槽——基类的分配函数会被继承。之后调用PyType_Ready并把类型对象加入模块与Custom基础例子完全相同。六、小结一个扩展类型模块的完整要素把五节内容串联起来一个可发布的 CPython 静态扩展类型模块需要齐备以下要素各要素在教程配套文件中的位置可以逐一核对对象结构体以PyObject_HEAD开头派生类型则以基类结构体开头见 custom.c、sublist.c类型对象PyTypeObject用 C99 指定初始化器填充按需设置tp_name使用真实点分路径兼顾pydoc/pickle、tp_basicsize、tp_flags、tp_new/tp_init/tp_dealloc、tp_members/tp_methods/tp_getset、tp_traverse/tp_clear模块定义PyModuleDefPy_mod_exec槽 PyInit_*入口在其中先PyType_Ready再PyModule_AddObjectRef派生类型先填tp_base打包与安装pyproject.tomlsetuptools 构建后端setup.py的Extension声明python -m pip install .安装行为验证仓库配套的 test.py 覆盖属性读写/删除、类型约束报错TypeError: The first attribute value must be a string、TypeError: an integer is required (got type str)和循环 GC 回收等全部边界行为。参考路径一览教程正文 newtypes_tutorial.rst示例源码 custom.c、custom2.c、custom3.c、custom4.c、sublist.c构建文件 pyproject.toml 与 setup.py基础头文件 object.hPyObject_HEAD第 65 行与 structmember.h。【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考