首页
/ 深入 CPython 类型系统:PyTypeObject 结构与 tp 槽位完全解析

深入 CPython 类型系统:PyTypeObject 结构与 tp 槽位完全解析

2026-09-06 13:03:04作者:吴年前Myrtle

在 CPython 的 C API 中,PyTypeObject 是对象体系的基石:每个 Python 对象的行为(repr、比较、迭代、GC、缓冲导出等)都由其类型对象里的一组 C 函数指针(即 "tp slots")决定。本文基于官方文档 typeobj.rst 并结合 Objects/typeobject.cInclude/cpython/object.h 的实际源码,完整梳理 PyTypeObject 的定义、每个槽位的语义与继承规则、tp_flags 位掩码、静态类型与堆类型的差异,以及四个协议扩展结构(number/mapping/sequence/buffer/async),并给出可直接参考的完整类型定义示例。读完后你可以独立编写一个自定义扩展类型的 C 实现。

PyTypeObject 在对象体系中的位置

类型对象本身也是对象,可以用 PyObject_*PyType_* 函数族处理。相比大多数标准类型,类型对象体积较大,原因是它存储了大量 C 函数指针,每个指针实现该类型功能的一小部分。类型对象的字段按结构体中出现的顺序描述,对应 Python 层的行为:例如 tp_repr 实现 repr()tp_as_number 下的子槽位实现 __add__ 等。

结构体的权威定义位于 Include/cpython/object.h。从源码结构看,该结构体在 tp_vectorcall 之后还包含若干 VM 内部字段(tp_watchedtp_versions_used_tp_iteritem_tp_cache),源码中明确注释 "Below here all fields are internal to the VM",扩展模块不应触碰:

// Include/cpython/object.h (节选)
struct _typeobject {
    PyObject_VAR_HEAD
    const char *tp_name; /* For printing, in format "<module>.<name>" */
    Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */

    /* Methods to implement standard operations */
    destructor tp_dealloc;
    Py_ssize_t tp_vectorcall_offset;
    getattrfunc tp_getattr;
    setattrfunc tp_setattr;
    PyAsyncMethods *tp_as_async;
    reprfunc tp_repr;
    PyNumberMethods *tp_as_number;
    PySequenceMethods *tp_as_sequence;
    PyMappingMethods *tp_as_mapping;
    hashfunc tp_hash;
    ternaryfunc tp_call;
    reprfunc tp_str;
    getattrofunc tp_getattro;
    setattrofunc tp_setattro;
    PyBufferProcs *tp_as_buffer;
    unsigned long tp_flags;
    const char *tp_doc;
    traverseproc tp_traverse;
    inquiry tp_clear;
    richcmpfunc tp_richcompare;
    Py_ssize_t tp_weaklistoffset;
    getiterfunc tp_iter;
    iternextfunc tp_iternext;
    PyMethodDef *tp_methods;
    PyMemberDef *tp_members;
    PyGetSetDef *tp_getset;
    PyTypeObject *tp_base;
    PyObject *tp_dict;
    descrgetfunc tp_descr_get;
    descrsetfunc tp_descr_set;
    Py_ssize_t tp_dictoffset;
    initproc tp_init;
    allocfunc tp_alloc;
    newfunc tp_new;
    freefunc tp_free;
    inquiry tp_is_gc;
    PyObject *tp_bases;
    PyObject *tp_mro;
    PyObject *tp_cache;
    void *tp_subclasses;
    PyObject *tp_weaklist;
    destructor tp_del;
    unsigned int tp_version_tag;
    destructor tp_finalize;
    vectorcallfunc tp_vectorcall;
    /* Below here all fields are internal to the VM */
    unsigned char tp_watched;
    uint16_t tp_versions_used;
    _Py_iteritemfunc _tp_iteritem;
    void *_tp_cache;
};

文档在 "PyTypeObject Definition" 一节中通过 literalinclude 引入了 Doc/includes/typestruct.h,其内容与上面的源码一致。

快速参考:tp 槽位与 Python 特殊方法的映射

下表完整继承自原文档的 "tp slots" 快速参考表。列含义:O = 该槽位在 PyBaseObject_Type 上被设置;T = 在 PyType_Type 上被设置;D = 默认值行为;I = 继承规则。

槽位 类型 特殊方法/属性 O T D I
<R> tp_name const char * __name__ X X
tp_basicsize Py_ssize_t X X X
tp_itemsize Py_ssize_t X X
tp_dealloc destructor X X X
tp_vectorcall_offset Py_ssize_t X X
(tp_getattr) getattrfunc __getattribute__, __getattr__ G
(tp_setattr) setattrfunc __setattr__, __delattr__ G
tp_as_async PyAsyncMethods * sub-slots %
tp_repr reprfunc __repr__ X X X
tp_as_number PyNumberMethods * sub-slots %
tp_as_sequence PySequenceMethods * sub-slots %
tp_as_mapping PyMappingMethods * sub-slots %
tp_hash hashfunc __hash__ X G
tp_call ternaryfunc __call__ X X
tp_str reprfunc __str__ X X
tp_getattro getattrofunc __getattribute__, __getattr__ X X G
tp_setattro setattrofunc __setattr__, __delattr__ X X G
tp_as_buffer PyBufferProcs * sub-slots %
tp_flags unsigned long X X ?
tp_doc const char * __doc__ X X
tp_traverse traverseproc X G
tp_clear inquiry X G
tp_richcompare richcmpfunc __lt____ge__ X G
(tp_weaklistoffset) Py_ssize_t X ?
tp_iter getiterfunc __iter__ X
tp_iternext iternextfunc __next__ X
tp_methods PyMethodDef [] X X
tp_members PyMemberDef [] X
tp_getset PyGetSetDef [] X X
tp_base PyTypeObject * __base__ X
tp_dict PyObject * __dict__ ?
tp_descr_get descrgetfunc __get__ X
tp_descr_set descrsetfunc __set__, __delete__ X
(tp_dictoffset) Py_ssize_t X ?
tp_init initproc __init__ X X X
tp_alloc allocfunc X ? ?
tp_new newfunc __new__ X X ? ?
tp_free freefunc X X ? ?
tp_is_gc inquiry X X
<tp_bases> PyObject * __bases__ ~
<tp_mro> PyObject * __mro__ ~
[tp_cache] PyObject *
[tp_subclasses] void * __subclasses__
[tp_weaklist] PyObject *
(tp_del) destructor
[tp_version_tag] unsigned int
tp_finalize destructor __del__ X
tp_vectorcall vectorcallfunc
[tp_watched] unsigned char

标记约定(继承自原文档脚注):

  • () 括号:槽位(事实上)已弃用;
  • <> 尖括号:应初始化为 NULL 并按只读处理(如 tp_basestp_mro);
  • [] 方括号:仅供内部使用;
  • <R> 前缀:必填字段,不得为 NULLtp_name)。

D 列符号:X 表示槽位为 NULLPyType_Ready 会填入该值;~ 表示 PyType_Ready 总会设置(该字段应保持 NULL);? 表示 PyType_Ready 视其他槽位而定。I 列符号:X 表示子类型中该槽位为 NULL 时会通过 PyType_Ready 继承基类的值;% 表示子结构体中的槽位逐个继承;G 表示仅与其他槽位组合时继承;? 表示情况复杂,需看具体槽位说明。部分槽位还会通过普通的属性查找链事实上被继承。

子槽位(sub-slots)参考

子槽位 类型 对应特殊方法
PyAsyncMethods.am_await unaryfunc __await__
PyAsyncMethods.am_aiter unaryfunc __aiter__
PyAsyncMethods.am_anext unaryfunc __anext__
PyAsyncMethods.am_send sendfunc
PyNumberMethods.nb_add binaryfunc __add__ / __radd__
PyNumberMethods.nb_inplace_add binaryfunc __iadd__
PyNumberMethods.nb_subtract binaryfunc __sub__ / __rsub__
PyNumberMethods.nb_inplace_subtract binaryfunc __isub__
PyNumberMethods.nb_multiply binaryfunc __mul__ / __rmul__
PyNumberMethods.nb_inplace_multiply binaryfunc __imul__
PyNumberMethods.nb_remainder binaryfunc __mod__ / __rmod__
PyNumberMethods.nb_inplace_remainder binaryfunc __imod__
PyNumberMethods.nb_divmod binaryfunc __divmod__ / __rdivmod__
PyNumberMethods.nb_power ternaryfunc __pow__ / __rpow__
PyNumberMethods.nb_inplace_power ternaryfunc __ipow__
PyNumberMethods.nb_negative unaryfunc __neg__
PyNumberMethods.nb_positive unaryfunc __pos__
PyNumberMethods.nb_absolute unaryfunc __abs__
PyNumberMethods.nb_bool inquiry __bool__
PyNumberMethods.nb_invert unaryfunc __invert__
PyNumberMethods.nb_lshift / nb_inplace_lshift binaryfunc __lshift__ / __rlshift____ilshift__
PyNumberMethods.nb_rshift / nb_inplace_rshift binaryfunc __rshift__ / __rrshift____irshift__
PyNumberMethods.nb_and / nb_inplace_and binaryfunc __and__ / __rand____iand__
PyNumberMethods.nb_xor / nb_inplace_xor binaryfunc __xor__ / __rxor____ixor__
PyNumberMethods.nb_or / nb_inplace_or binaryfunc __or__ / __ror____ior__
PyNumberMethods.nb_int unaryfunc __int__
PyNumberMethods.nb_reserved void *
PyNumberMethods.nb_float unaryfunc __float__
PyNumberMethods.nb_floor_divide / nb_inplace_floor_divide binaryfunc __floordiv____ifloordiv__
PyNumberMethods.nb_true_divide / nb_inplace_true_divide binaryfunc __truediv____itruediv__
PyNumberMethods.nb_index unaryfunc __index__
PyNumberMethods.nb_matrix_multiply / nb_inplace_matrix_multiply binaryfunc __matmul__ / __rmatmul____imatmul__
PyMappingMethods.mp_length lenfunc __len__
PyMappingMethods.mp_subscript binaryfunc __getitem__
PyMappingMethods.mp_ass_subscript objobjargproc __setitem__ / __delitem__
PySequenceMethods.sq_length lenfunc __len__
PySequenceMethods.sq_concat binaryfunc __add__
PySequenceMethods.sq_repeat ssizeargfunc __mul__
PySequenceMethods.sq_item ssizeargfunc __getitem__
PySequenceMethods.sq_ass_item ssizeobjargproc __setitem__ / __delitem__
PySequenceMethods.sq_contains objobjproc __contains__
PySequenceMethods.sq_inplace_concat binaryfunc __iadd__
PySequenceMethods.sq_inplace_repeat ssizeargfunc __imul__
PyBufferProcs.bf_getbuffer getbufferproc __buffer__
PyBufferProcs.bf_releasebuffer releasebufferproc __release_buffer__

槽位函数指针类型(slot typedefs)

以下是原文档给出的全部槽位 typedef 及其参数/返回值,编写实现函数时必须严格遵守:

typedef 参数类型 返回类型
allocfunc PyTypeObject *Py_ssize_t PyObject *
destructor PyObject * void
freefunc void * void
traverseproc PyObject *visitprocvoid * int
newfunc PyTypeObject *PyObject *PyObject * PyObject *
initproc PyObject *PyObject *PyObject * int
reprfunc PyObject * PyObject *
getattrfunc PyObject *const char * PyObject *
setattrfunc PyObject *const char *PyObject * int
getattrofunc PyObject *PyObject * PyObject *
setattrofunc PyObject *PyObject *PyObject * int
descrgetfunc PyObject *PyObject *PyObject * PyObject *
descrsetfunc PyObject *PyObject *PyObject * int
hashfunc PyObject * Py_hash_t
richcmpfunc PyObject *PyObject *int PyObject *
getiterfunc PyObject * PyObject *
iternextfunc PyObject * PyObject *
lenfunc PyObject * Py_ssize_t
getbufferproc PyObject *Py_buffer *int int
releasebufferproc PyObject *Py_buffer * void
inquiry PyObject * int
unaryfunc PyObject * PyObject *
binaryfunc PyObject *PyObject * PyObject *
ternaryfunc PyObject *PyObject *PyObject * PyObject *
ssizeargfunc PyObject *Py_ssize_t PyObject *
ssizeobjargproc PyObject *Py_ssize_tPyObject * int
objobjproc PyObject *PyObject * int
objobjargproc PyObject *PyObject *PyObject * int

其中 allocfunc 的契约值得特别注意:它只负责"分内存",不负责"初始化"——应返回长度足够、对齐正确、清零且 ob_refcnt == 1ob_type 指向类型参数的内存块;若 tp_itemsize 非零,需把 ob_size 初始化为 nitems,内存块长度为 tp_basicsize + nitems * tp_itemsize 向上取整到 sizeof(void*) 的倍数;除此之外不得做任何实例初始化(包括分配额外内存),那属于 tp_new 的职责。

各槽位详解:语义、继承与默认值

PyObject / PyVarObject 部分

PyTypeObject 扩展自 PyVarObject

  • ob_refcnt:由 PyObject_HEAD_INIT 宏初始化为 1。注意:静态类型的实例(ob_type 指回该类型的对象)不计入对类型的引用;而堆分配类型的实例计入引用。该字段不被子类型继承。

  • ob_type:类型对象自身的元类型,正常应为 &PyType_Type。对需要在 Windows 上可动态加载的扩展模块,编译器不允许把 &PyType_Type 作为静态初始化值,约定是向 PyObject_HEAD_INITNULL,然后在模块初始化函数开头显式赋值:

    Foo_Type.ob_type = &PyType_Type;
    

    这必须在创建任何实例之前完成。PyType_Ready 会检查 ob_type 是否为 NULL,若是则用基类的 ob_type 初始化;非零时不改动。该字段被子类型继承。

  • ob_size:静态类型应初始化为 0;堆类型(由 type_new 创建,通常来自 class 语句)该字段有特殊的内部含义,必须通过 Py_SIZE() / Py_SET_SIZE() 宏访问。不被继承。

命名与尺寸:tp_name、tp_basicsize、tp_itemsize

  • tp_name:唯一必填字段。可被模块全局访问的类型应使用"完整模块名 + 点 + 类型名",如包 P 中子包 Q 的模块 M 里定义的 Ttp_name 应为 "P.Q.M.T"。堆类型只需类型名本身,模块名应显式存入类型字典的 '__module__' 键。静态类型中 tp_name 应包含点号:最后一个点之前映射为 __module__,之后映射为 __name__;若没有点号,整个字段成为 __name____module__ 未定义——这会导致类型无法被 pickle,也不会出现在 pydoc 生成的模块文档中。
  • tp_basicsize / tp_itemsize:决定实例字节大小。tp_itemsize 为 0 表示定长实例,所有实例大小都是 tp_basicsize(可用 PyUnstable_Object_GC_NewWithExtraData 打破这一规则);非零表示变长实例,实例必须有 ob_size 字段,大小为 tp_basicsize + N * tp_itemsizePyObject_NewVar 会把 N 存入 ob_size。注意两点:
    • ob_size 后续可能被挪作他用。例如 int 实例以实现定义的方式使用 ob_size 的位;访问底层存储应使用 PyLong_Exportlist 实例是定长的却带有 ob_size 字段,读长度应调用 PyList_Size 而非直接读 ob_size
    • 正确设置 tp_basicsize 的方式是对声明实例布局的结构体使用 sizeof,该结构体必须包含基类型结构体,因此 tp_basicsize 必须 ≥ 基类的 tp_basicsize。由于每个类型都是 object 的子类型,结构体必须包含 PyObject_HEADPyObject_VAR_HEADtp_basicsize 不包含 GC 头大小;它必须是 _Alignof(PyObject) 的倍数,若可变部分有特殊对齐要求,tp_basicsizetp_itemsize 都必须是该对齐值的倍数(如可变部分存 double,两者都须是 _Alignof(double) 的倍数)。
    • 继承规则:两个字段各自独立继承——置 0 表示"实例不需要额外存储",PyType_Ready 会从基类拷贝值。若基类 tp_itemsize 非零,子类型一般不应改为另一个非零值。

生命周期槽位:alloc / new / init / dealloc / free / clear / traverse / finalize

tp_allocallocfunc):签名 PyObject *tp_alloc(PyTypeObject *self, Py_ssize_t nitems)。静态子类型继承该槽位(继承自 object 时为 PyType_GenericAlloc);堆子类型不继承,恒为 PyType_GenericAlloc

tp_newnewfunc):签名 PyObject *tp_new(PyTypeObject *subtype, PyObject *args, PyObject *kwds)subtype 是被创建对象的类型,未必等于提供该 tp_new 的类型本身(可以是其子类型)。实现应调用 subtype->tp_alloc(subtype, nitems) 分配内存,然后只做"绝对必要"的初始化;可以被忽略或重复的初始化放入 tp_init。经验法则:不可变类型的全部初始化放在 tp_new,可变类型的大部分初始化推迟到 tp_init。静态类型无默认值:tp_newNULL 时该类型不能被调用创建实例(通常意味着实例由工厂函数创建)。

tp_initinitproc):签名 int tp_init(PyObject *self, PyObject *args, PyObject *kwds),对应 __init__。当通过调用类型正常创建实例时,在 tp_new 返回该类型的实例后被调用;若 tp_new 返回了其他(非子类型的)类型的实例,则不调用 tp_init;返回子类型实例时调用子类型的 tp_init。成功返回 0,错误返回 -1 并设置异常。

tp_deallocdestructor):签名 void tp_dealloc(PyObject *self)。职责有三:移除实例拥有的所有引用(如调用 Py_CLEAR)、释放实例拥有的所有内存缓冲、调用 tp_free 释放对象本身。关键约束:

  • 若调用了可能设置错误指示器的函数,必须用 PyErr_GetRaisedException / PyErr_SetRaisedException 备份和恢复,避免破坏可能已存在的异常:

    static void
    foo_dealloc(foo_object *self)
    {
        PyObject *exc = PyErr_GetRaisedException();
        ...
        PyErr_SetRaisedException(exc);
    }
    
  • dealloc 本身不得抛出异常;遇到错误应调用 PyErr_FormatUnraisable 记录并清除不可抛异常。

  • 建议开头调用 PyObject_CallFinalizerFromDealloc,保证对象在被销毁前总是先被 finalize。

  • 若类型支持 GC(Py_TPFLAGS_HAVE_GC),在清理成员字段前必须先调用 PyObject_GC_UnTrack

  • 允许在 dealloc 中调用 tp_clear 以减少代码重复,但注意 tp_clear 可能已被调用过。

  • 若类型是堆分配(Py_TPFLAGS_HEAPTYPE),deallocator 应在调用类型 deallocator 之后释放对类型对象自身的引用(Py_DECREF)。

  • 文档给出的完整参考实现:

    static void
    foo_dealloc(PyObject *self)
    {
        PyObject *exc = PyErr_GetRaisedException();
    
        if (PyObject_CallFinalizerFromDealloc(self) < 0) {
            // self was resurrected.
            goto done;
        }
    
        PyTypeObject *tp = Py_TYPE(self);
    
        if (tp->tp_flags & Py_TPFLAGS_HAVE_GC) {
            PyObject_GC_UnTrack(self);
        }
    
        // Optional, but convenient to avoid code duplication.
        if (tp->tp_clear && tp->tp_clear(self) < 0) {
            PyErr_WriteUnraisable(self);
        }
    
        // Any additional destruction goes here.
    
        tp->tp_free(self);
        self = NULL;  // In case PyErr_WriteUnraisable() is called below.
    
        if (tp->tp_flags & Py_TPFLAGS_HEAPTYPE) {
            Py_CLEAR(tp);
        }
    
    done:
        if (PyErr_Occurred()) {
            PyErr_WriteUnraisable(self);
        }
        PyErr_SetRaisedException(exc);
    }
    
  • tp_dealloc 可能从任意 Python 线程被调用(对象若进入引用环,可能在任意线程的 GC 中被回收)。

tp_freefreefunc):签名 void tp_free(void *self),必须释放 tp_alloc 分配的内存。静态子类型继承该槽位(继承自 object 时为 PyObject_Free;例外:若类型开启 GC 且将继承 PyObject_Free,则改为默认为 PyObject_GC_Del);堆子类型不继承,默认是与 PyType_GenericAlloc 及 GC 标志匹配的释放函数。

tp_traversetraverseproc):GC 遍历函数,仅在 Py_TPFLAGS_HAVE_GC 设置时生效。与 tp_clear、GC 标志位三件套成组继承:三者全为零时从基类整体继承。

tp_clearinquiry):签名 int tp_clear(PyObject *)。目的是打断导致孤立环(cyclic isolate)的引用循环,使对象可以被安全销毁。被 clear 的对象处于"部分销毁"状态,不必满足正常使用的不变量。要点:

  • 无需要清掉不能参与引用环的引用(字符串、整数),但实践中常清掉所有引用,并让 tp_dealloc 复用 tp_clear

  • 非平凡的清理应放在 tp_finalize 而非 tp_clear

  • tp_clear 不能打断某个环,环中的对象可能永远不可回收(泄漏),见 gc.garbage

  • 被引用对象可能已经被 clear,不保证处于一致状态;tp_clear 可能从任意线程调用。

  • 对象不保证在 tp_dealloc 前被自动 clear。

  • 实现中应把成员引用置 NULL,并使用 Py_CLEAR 宏——因为引用释放(Py_DECREF)可能在把指针置 NULL 之前就触发被包含对象的回收链(含 finalizer、weakref 回调等任意 Python 代码),而这些代码可能再次引用 self,此时被包含对象的指针必须已经是 NULL

    static int
    local_clear(PyObject *op)
    {
        localobject *self = (localobject *) op;
        Py_CLEAR(self->key);
        Py_CLEAR(self->args);
        Py_CLEAR(self->kw);
        Py_CLEAR(self->dict);
        return 0;
    }
    
  • 若设置了 Py_TPFLAGS_MANAGED_DICT,clear 函数必须调用 PyObject_ClearManagedDict((PyObject*)self)

  • 一个微妙但重要的结论:全系统所有 tp_clear 函数合起来必须能打断所有引用环。例如 tuple 类型没有 tp_clear,因为可以证明不可能只由 tuple 组成引用环,所以含 tuple 的环由其他类型的 tp_clear 负责打断。若不确定,就提供 tp_clear

tp_finalizedestructor,3.4 引入):签名 void tp_finalize(PyObject *self),是 __del__ 的 C 实现。finalize 的目的是在对象与它直接或间接引用的对象仍处一致状态时执行非平凡清理,且允许执行任意 Python 代码。约束与保证:

  • Python 自动 finalize 一个对象前,其直接或间接被引用者可能已被 finalize,但都不会被 clear;其他未 finalize 的对象可能还在使用该对象,因此 finalizer 必须把对象留在健全状态。

  • 自动 finalize 后,Python 可能开始 clear 该对象及其被引用者,被 clear 的对象不保证一致状态,因此 finalizer 必须能容忍"被清空的被引用者"。

  • 对象不保证在 tp_dealloc 之前被自动 finalize,建议在 dealloc 开头调用 PyObject_CallFinalizerFromDealloc

  • tp_finalize 可从任意线程调用(持 GIL),也可能在解释器关闭、部分全局变量已被删除时调用。

  • 自动 finalize 的算法与保证:CPython 目前只把开启 GC 的对象标记为 finalized;finalizer 若使对象重新可达即"复活"对象,取消待销毁流程;Python 不会 finalize 可达对象、不会在 clear 之后 finalize、不会并发 finalize 同一环的两个成员、finalize 一个环的任何成员前会先 finalize 全部成员;要手动 finalize 应调用 PyObject_CallFinalizer / PyObject_CallFinalizerFromDealloc 而非直接调槽位。

  • finalizer 应保持不变量:推荐写法是先 PyErr_GetRaisedException 备份异常,中间出错用 PyErr_WriteUnraisable / PyErr_FormatUnraisable 记录,最后 PyErr_SetRaisedException 恢复:

    static void
    foo_finalize(PyObject *self)
    {
        PyObject *exc = PyErr_GetRaisedException();
    
        if (do_something_that_might_raise() != success_indicator) {
            PyErr_WriteUnraisable(self);
            goto done;
        }
    
    done:
        PyErr_SetRaisedException(exc);
    }
    

    3.8 起不再需要设置 Py_TPFLAGS_HAVE_FINALIZE 才生效。

tp_is_gcinquiry):签名 int tp_is_gc(PyObject *self)。GC 需要判断某个具体对象是否可收集;通常看 Py_TPFLAGS_HAVE_GC 即可,但静态/动态混合分配实例的类型应提供该函数,可收集返回 1,否则返回 0。唯一使用它的例子是 PyType_Type 本身(区分静态与堆类型)。为 NULLPy_TPFLAGS_HAVE_GC 起等效作用。

属性访问、比较、迭代与调用

  • tp_getattro / tp_setattro(推荐)与 tp_getattr / tp_setattr(已弃用):属性名以 C 字符串还是 Python 字符串对象传递的两种旧接口。两者成组继承:子类型的两个字段都为 NULL 时才从基类同时继承。默认实现 PyObject_GenericGetAttr / PyObject_GenericSetAttrPyBaseObject_Type 提供。setattro 必须支持 value == NULL 表示删除属性。
  • tp_repr:签名同 PyObject_Repr,必须返回 str 对象。理想情况下返回值能被 eval 还原出同值对象;做不到就返回 '<...>' 形式的可推断字符串。默认输出 <%s object at %p>
  • tp_str:签名同 PyObject_Str,应返回"友好"字符串(print 使用)。未设置时回退到 PyObject_Repr
  • tp_hash:签名 Py_hash_t tp_hash(PyObject *)-1 只能作为错误返回值(需设置异常);未设置且 tp_richcompare 也未设置时,哈希对象抛 TypeError(等价于 PyObject_HashNotImplemented)。显式设为 PyObject_HashNotImplemented 可阻断从父类型继承 hash,等价于 Python 层的 __hash__ = None。与 tp_richcompare 成组继承。
  • tp_richcompare:签名 PyObject *tp_richcompare(PyObject *self, PyObject *other, int op)。第一个参数保证是该类型实例。比较未定义时返回 Py_NotImplemented;出错返回 NULL 并设异常。操作常量:Py_LT<)、Py_LE<=)、Py_EQ==)、Py_NE!=)、Py_GT>)、Py_GE>=)。宏 Py_RETURN_RICHCOMPARE(VAL_A, VAL_B, op)(3.7+)可简化实现,返回新强引用。PyBaseObject_Type 提供默认实现,但若只定义了 tp_hash,继承的比较函数也不会被使用。
  • tp_callternaryfunc):签名同 PyObject_Call;不可调用对象应为 NULL
  • tp_vectorcall / tp_vectorcall_offset:vectorcall 协议(更高效的调用路径)。tp_vectorcall_offset 是实例中 vectorcallfunc 指针字段的偏移,仅在 Py_TPFLAGS_HAVE_VECTORCALL 设置时使用;指针可为 NULL,此时回退到 tp_call。设置该标志的类必须同时设置 tp_call 且行为一致(可设为 PyVectorcall_Call)。3.8 之前该槽位叫 tp_print;3.12 起,用户类在 Python 层重新赋值 __call__ 会清除 Py_TPFLAGS_HAVE_VECTORCALL 标志以避免不一致。tp_vectorcall(3.9 起生效,字段 3.8 引入)用于优化类型对象本身的调用(即 type.__call__ 路径):若为 NULL 则走 Py_TYPE(type)->tp_call;它必须与 type->tp_call 行为一致(默认元类下即:调用 type->tp_new,结果若是 type 的子类型则调用 type->tp_init,返回 tp_new 的结果)。该字段永不继承。
  • tp_itergetiterfunc)与 tp_iternextiternextfunc):tp_iter 存在通常意味着实例可迭代;tp_iternext 在迭代器耗尽时必须返回 NULL(可以设置也可以不设置 StopIteration),其他错误同样返回 NULL。迭代器类型还应定义 tp_iter 并返回迭代器自身而非新迭代器。
  • tp_descr_get / tp_descr_set:描述符协议,签名分别为 PyObject *(PyObject *self, PyObject *obj, PyObject *type)int (PyObject *self, PyObject *obj, PyObject *value)value == NULL 表示删除)。被子类型继承。

方法与成员声明数组

  • tp_methodsPyMethodDef *):NULL 结尾的静态数组,声明常规方法;每个条目会向类型字典加入一个方法描述符。不被继承(方法通过其他机制继承)。
  • tp_membersPyMemberDef *):声明实例的数据成员;每个条目向类型字典加入成员描述符。不被继承。
  • tp_getsetPyGetSetDef *):声明计算属性;每个条目向类型字典加入 getset 描述符。不被继承。

继承结构与内部槽位

  • tp_base:C 层仅支持单继承;多继承需要动态创建类型(调用元类型)。由于 C99 地址常量初始化规则(&PyBaseObject_Type 这类一元 & 取址在部分编译器下不构成地址常量,MSVC 不支持),tp_base 应在模块 init 函数中设置。默认为 &PyBaseObject_Type。不被继承。
  • tp_dict:由 PyType_Ready 存放类型字典。应在调用 PyType_Ready 前初始化为 NULL(或初始属性字典)。类型初始化完成后只能加入非重载操作的属性,之后应视为只读。不得PyDict_SetItem 等字典 C API 直接修改。某些类型的字典并不存放在该槽位,取任意类型的字典请用 PyType_GetDict。3.12 起的内部细节:静态内置类型的 tp_dict 恒为 NULL,其字典改存于 PyInterpreterState
  • tp_dictoffset / tp_weaklistoffset:实例字典与弱引用链表头的实例内偏移,分别被 PyObject_GenericGetAttrPyObject_ClearWeakRefs / PyWeakref_* 使用。二者应视为只写——取字典指针用 PyObject_GenericGetDict(可能需要分配内存),直接访问属性更建议走 PyObject_GetAttr。注意 tp_dictoffset(实例字典)与 tp_dict(类型自身字典)不要混淆;tp_weaklistoffsettp_weaklist(对类型对象的弱引用表头)也不要混淆。文档建议优先使用 Py_TPFLAGS_MANAGED_DICT / Py_TPFLAGS_MANAGED_WEAKREF 替代这两个字段:若设置了对应标志,相应偏移会被设为 -1 表示"不安全使用";同时设置标志与偏移是错误。子类型不应覆盖 tp_dictoffset(C 代码可能按旧偏移访问,不安全),需要支持继承场景时应改用 Py_TPFLAGS_MANAGED_DICT
  • tp_bases / tp_mro:均为只读,初始化为 NULL,由 PyType_Ready 填充(MRO 为以自身开始、object 结束的元组)。静态类型使用多继承效果不佳:设置 tp_bases 元组不会报错,但部分槽位只会从第一个基类继承。
  • tp_cache / tp_subclasses / tp_weaklist / tp_version_tag / tp_watched:均为内部使用。tp_subclasses 自 3.12 起类型改为 void *(某些类型上不是有效的 PyObject*),取子类列表应调用 type.__subclasses__()tp_version_tag 用于方法缓存索引,3.12 起静态内置类型的弱引用存放在 PyInterpreterState 上,tp_weaklist 对它们恒为 NULL
  • tp_del:已弃用,请使用 tp_finalize

tp_flags 位掩码

tp_flags 是位掩码:部分标志指示特定场景下的变体语义,部分标志表示"某些历史上并非始终存在的字段现在有效"——若标志位未置位,其守护的字段必须视为 0 或 NULL,不得访问。默认值(PyBaseObject_Type)为 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE。检查是否具备某特性用 PyType_HasFeature(tp, f)(即 tp->tp_flags & f)。主要标志及其继承规则:

标志 含义 继承规则
Py_TPFLAGS_HEAPTYPE 类型对象本身在堆上分配(如 PyType_FromSpec 创建);实例的 ob_type 视为对类型的引用,实例创建时 INCREF、销毁时 DECREF;堆类型还应支持 GC(可能与其模块对象成环) 文档标注 "???"(未明确)
Py_TPFLAGS_BASETYPE 可作为其他类型的基类;未置位则不可被继承(类似 Java 的 final 类) "???"
Py_TPFLAGS_READY 类型已被 PyType_Ready 完整初始化 "???"
Py_TPFLAGS_READYING PyType_Ready 正在初始化该类型 "???"
Py_TPFLAGS_HAVE_GC 实例支持 GC:tp_alloc 必须用 PyObject_GC_New / PyType_GenericAlloc 分配,tp_freePyObject_GC_Del 释放 tp_traversetp_clear 成组继承:三者全为零时从基类整体继承
Py_TPFLAGS_DEFAULT 所有与"字段存在性"相关位位的合集,当前仅含 Py_TPFLAGS_HAVE_STACKLESS_EXTENSION "???"
Py_TPFLAGS_METHOD_DESCRIPTOR 对象行为类似未绑定方法(3.8+):meth.__get__(obj, cls)(*args) 等价于 meth(obj, *args);使解释器对 obj.meth() 跳过临时 bound method 对象。对未设 Py_TPFLAGS_IMMUTABLETYPE 的类型永不继承;扩展类型在 tp_descr_get 被继承时随之继承 见左
Py_TPFLAGS_MANAGED_DICT(3.12+) 实例有 __dict__ 且由 VM 管理;必须同时设 Py_TPFLAGS_HAVE_GC;traverse 必须调 PyObject_VisitManagedDict,clear 必须调 PyObject_ClearManagedDict 除非父类设置了 tp_dictoffset
Py_TPFLAGS_MANAGED_WEAKREF(3.12+) 实例可被弱引用;必须同时设 Py_TPFLAGS_HAVE_GC 除非父类设置了 tp_weaklistoffset
Py_TPFLAGS_PREHEADER(3.12+) 等价于 `MANAGED_DICT MANAGED_WEAKREF`;依赖 VM 实现,值不稳定,建议直接使用单个标志
Py_TPFLAGS_ITEMS_AT_END(3.12+) 仅限变长类型:可变部分位于实例内存末尾,偏移为 Py_TYPE(obj)->tp_basicsize;需保证所有超类使用该布局或不是变长的(Python 不检查) 被继承
Py_TPFLAGS_LONG_SUBCLASS / LIST / TUPLE / BYTES / UNICODE / DICT / BASE_EXC / TYPE PyType_FastSubclass 快速判断是否为内置类型子类型(如 PyLong_Check 调用);比 PyObject_IsInstance 快。继承自内置类型的自定义类型应正确设置这些标志,否则不同检查路径行为会不一致
Py_TPFLAGS_HAVE_FINALIZE(3.4,3.8 弃用) 曾表示 tp_finalize 存在;现解释器假设该槽位始终存在,无需再设
Py_TPFLAGS_HAVE_VECTORCALL(3.8 起 _Py_TPFLAGS_HAVE_VECTORCALL,3.9 更名) 类实现了 vectorcall 协议,见 tp_vectorcall_offset tp_call 被继承时随之继承;3.12 起类重新赋值 __call__ 时移除,且可变类也可以继承
Py_TPFLAGS_IMMUTABLETYPE(3.10+) 类型对象不可变:不能设置/删除类型属性;PyType_Ready 会自动为静态类型应用 不继承
Py_TPFLAGS_DISALLOW_INSTANTIATION(3.10+) 禁止创建实例:tp_newNULL、类型字典不建 __new__ 键。必须在创建类型前(如 PyType_Ready 之前)设置;静态类型若 tp_baseNULL/&PyBaseObject_Typetp_newNULL 时自动设置 不继承;子类除非提供非 NULLtp_new(仅 C API 可能),否则同样不可实例化。注意:想让基类不可实例化但子类可以(抽象基类),不要用该标志,而应让 tp_new 仅对子类成功
Py_TPFLAGS_MAPPING(3.10+) 实例可作为 match 块的 mapping pattern 主体;注册/继承 collections.abc.Mapping 时自动置位,注册 Sequence 时清除;与 Py_TPFLAGS_SEQUENCE 互斥,同时启用是错误 由未设置 SEQUENCE 的类型继承
Py_TPFLAGS_SEQUENCE(3.10+) 实例可作为 match 块的 sequence pattern 主体;与 MAPPING 互斥 由未设置 MAPPING 的类型继承
Py_TPFLAGS_VALID_VERSION_TAG 内部标志,勿动;类变更应调用 PyType_Modified
Py_TPFLAGS_HAVE_VERSION_TAG 空操作宏(3.13 软弃用),历史上表示 tp_version_tag 可用
Py_TPFLAGS_INLINE_VALUES(3.13+) 实例的 "inline values" 数组(属性存储)紧跟对象末尾;要求设置 Py_TPFLAGS_HAVE_GC 不继承
Py_TPFLAGS_IS_ABSTRACT 抽象类型,不可实例化(见 abc 模块) 不继承
Py_TPFLAGS_HAVE_STACKLESS_EXTENSION 内部保留(历史上供 Stackless Python),勿动,可能在未来移除

PyType_Ready:默认值填充与继承的实际发生地

文档反复提到的 "PyType_Ready 会填充 NULL 槽位、计算 MRO、建立类型字典" 等行为,其实现位于 Objects/typeobject.c。从源码可以确认:

  • 公共入口 PyType_Ready:若 tp_flags 已有 Py_TPFLAGS_READY 直接返回 0;否则,非堆类型会被加上 Py_TPFLAGS_IMMUTABLETYPE 并通过 _Py_SetImmortalUntracked 置为永生("Static types must be immortal"),然后在类型锁内调用 type_ready(type, 1, 1)
  • 内部函数 type_ready 完成继承槽位拷贝、MRO 计算(源码中 MRO 计算路径会调用类型对象的 mro 方法)、fixup_slot_dispatchers(把 Python 层定义的特殊方法映射回 C 槽位分发器)等工作,成功后置 Py_TPFLAGS_READY
  • 静态内置类型不走 PyType_Ready 公开路径,而由 init_static_typetype_ready 并打上 _Py_TPFLAGS_STATIC_BUILTIN 标志,这也解释了为什么静态类型的 tp_dict / tp_weaklist 存放在 PyInterpreterState 而非结构体内。

因此文档中每个槽位的 "Default" 小节(如 "PyType_Ready 会在其为 NULL 时填入此值")对应的是 type_ready 中的继承与默认逻辑;"Inheritance" 小节描述的是同一流程中对基类槽位的拷贝规则。

静态类型与堆类型

静态类型(static types):C 代码中直接定义静态 PyTypeObject 结构体并用 PyType_Ready 初始化。相对 Python 定义的类型,其限制为:

  • 只能有一个基类,不能用多继承;
  • 类型对象(其实例不一定)不可变,无法从 Python 增改类型对象属性——实现上 PyType_Ready 会自动加上 Py_TPFLAGS_IMMUTABLETYPE,与 Objects/typeobject.c 的源码一致;
  • 静态类型对象跨子解释器共享,不应包含子解释器专属状态(与 Objects/typeobject.cinit_static_type 按解释器管理弱引用等状态的做法相印证);
  • 由于 PyTypeObject 在 Limited API 中只是不透明结构(参见 Include/pytypedefs.h 的前向声明),使用静态类型的扩展模块必须针对特定 Python 次版本编译。

堆类型(heap types):与 class 语句创建的类一一对应,带有 Py_TPFLAGS_HEAPTYPE 标志。做法是填充 PyType_Spec 结构体并调用 PyType_FromSpecPyType_FromSpecWithBasesPyType_FromModuleAndSpecPyType_FromMetaclass

协议扩展结构

数值协议:PyNumberMethods

PyNumberMethods 保存对象实现数值协议的函数指针,各槽位与同名 number 一节 中记录的函数配套使用。结构体定义(继承自原文档):

typedef struct {
     binaryfunc nb_add;
     binaryfunc nb_subtract;
     binaryfunc nb_multiply;
     binaryfunc nb_remainder;
     binaryfunc nb_divmod;
     ternaryfunc nb_power;
     unaryfunc nb_negative;
     unaryfunc nb_positive;
     unaryfunc nb_absolute;
     inquiry nb_bool;
     unaryfunc nb_invert;
     binaryfunc nb_lshift;
     binaryfunc nb_rshift;
     binaryfunc nb_and;
     binaryfunc nb_xor;
     binaryfunc nb_or;
     unaryfunc nb_int;
     void *nb_reserved;
     unaryfunc nb_float;

     binaryfunc nb_inplace_add;
     binaryfunc nb_inplace_subtract;
     binaryfunc nb_inplace_multiply;
     binaryfunc nb_inplace_remainder;
     ternaryfunc nb_inplace_power;
     binaryfunc nb_inplace_lshift;
     binaryfunc nb_inplace_rshift;
     binaryfunc nb_inplace_and;
     binaryfunc nb_inplace_xor;
     binaryfunc nb_inplace_or;

     binaryfunc nb_floor_divide;
     binaryfunc nb_true_divide;
     binaryfunc nb_inplace_floor_divide;
     binaryfunc nb_inplace_true_divide;

     unaryfunc nb_index;

     binaryfunc nb_matrix_multiply;
     binaryfunc nb_inplace_matrix_multiply;
} PyNumberMethods;

两条实现纪律:

  • 二元/三元函数必须检查所有操作数的类型并完成必要转换(至少一个操作数是该类型实例);操作未定义时返回 Py_NotImplemented;其他错误返回 NULL 并设置异常。
  • nb_reserved 必须恒为 NULL(Python 3.0.1 前叫 nb_long)。

映射协议:PyMappingMethods

三个成员:

  • mp_lengthlenfunc):被 PyMapping_SizePyObject_Size 使用,签名相同;对象无长度时可设为 NULL
  • mp_subscriptbinaryfunc):被 PyObject_GetItemPySequence_GetSlice 使用;PyMapping_Check 返回 1 必须填写该槽位。
  • mp_ass_subscriptobjobjargproc):被 PyObject_SetItemPyObject_DelItemPySequence_SetSlicePySequence_DelSlice 使用;vNULL 表示删除项;NULL 表示不支持项的赋值/删除。

序列协议:PySequenceMethods

  • sq_lengthlenfunc):被 PySequence_Size / PyObject_Size 使用;也用于经 sq_item / sq_ass_item 处理负索引。
  • sq_concatbinaryfunc):被 PySequence_Concat 使用;也用于 + 运算符——在 nb_add 数值加法尝试失败之后。
  • sq_repeatssizeargfunc):被 PySequence_Repeat 使用;也用于 * 运算符(在 nb_multiply 之后)。
  • sq_itemssizeargfunc):被 PySequence_GetItem 使用;也被 PyObject_GetItem 使用(在 mp_subscript 之后)。PySequence_Check 返回 1 必须填写该槽位。负索引处理:若 sq_length 已填写则用其长度换算为正索引再传给 sq_item;否则索引原样传递。
  • sq_ass_itemssizeobjargproc):被 PySequence_SetItem 使用;也被 PyObject_SetItem / PyObject_DelItem 使用(在 mp_ass_subscript 之后);NULL 表示不支持。
  • sq_containsobjobjproc):可被 PySequence_Contains 使用;NULL 时该函数退化为线性遍历直到找到匹配。
  • sq_inplace_concat / sq_inplace_repeat:被 PySequence_InPlaceConcat / PySequence_InPlaceRepeat 使用,应就地修改第一个操作数并返回它;NULL 时分别回退到 PySequence_Concat / PySequence_Repeat;也用于增广赋值 += / *=(在对应 nb_inplace_* 之后)。

缓冲协议:PyBufferProcs

PyBufferProcs 实现缓冲协议,定义 exporter 如何向 consumer 暴露内部数据:

bf_getbufferint (PyObject *exporter, Py_buffer *view, int flags)):处理 exporter 的导出请求,除第 (3) 步外必须执行:(1) 判断能否满足请求,不能则抛 BufferError、置 view->obj = NULL 并返回 -1;(2) 填写请求的字段;(3) 递增内部导出计数;(4) 置 view->obj 为 exporter 并 INCREF;(5) 返回 0。

free-threaded 构建下的线程安全要求:导出计数递增必须原子;底层缓冲数据必须在所有导出的生命周期内有效且地址稳定;支持扩容/重分配的对象(如 bytearray)在执行此类操作前必须原子检查导出计数,有导出时抛 BufferError;函数需支持多线程并发调用。

缓冲提供者链/树可用两种方案:Re-export(每个节点把自己作为 exporter,view->obj 指向自己的新引用)或 Redirect(请求重定向到树根,view->obj 指向根对象的新引用)。Py_buffer 指向的所有内存属于 exporter,必须在没有 consumer 前保持有效;formatshapestridessuboffsetsinternal 字段对 consumer 只读。PyBuffer_FillInfo 可以简单暴露 bytes 缓冲并正确处理所有请求类型;consumer 侧接口是 PyObject_GetBuffer

bf_releasebuffervoid (PyObject *exporter, Py_buffer *view)):处理释放请求,无资源可释放时可以为 NULL;标准实现:(1) 递减内部导出计数;(2) 计数归零时释放 view 关联的全部内存。free-threaded 构建下计数递减必须原子,计数归零时的资源清理也必须原子(最后一次释放可能与其他线程的并发释放竞争,释放只能发生一次)。exporter 必须用 Py_buffer.internal 字段跟踪缓冲专属资源(该字段保证恒定,而 consumer 可能传入 view 的副本)。该函数不得 DECREF view->obj——由 PyBuffer_Release 自动完成(该设计有利于打破引用环)。

异步协议:PyAsyncMethods(3.5 引入)

typedef struct {
    unaryfunc am_await;
    unaryfunc am_aiter;
    unaryfunc am_anext;
    sendfunc am_send;
} PyAsyncMethods;
  • am_awaitPyObject *am_await(PyObject *self),返回值必须是迭代器(PyIter_Check 返回 1);非 awaitable 可置 NULL
  • am_aiterPyObject *am_aiter(PyObject *self),必须返回异步迭代器对象;未实现异步迭代协议可置 NULL
  • am_anextPyObject *am_anext(PyObject *self),必须返回 awaitable 对象;可置 NULL
  • am_send(3.10 引入):PySendResult am_send(PyObject *self, PyObject *arg, PyObject **result),见 PyIter_Send;可置 NULL

完整示例:从零定义一个静态类型

以下示例完整继承自原文档 Examples 一节。

最基本的静态类型

typedef struct {
    PyObject_HEAD
    const char *data;
} MyObject;

static PyTypeObject MyObject_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "mymod.MyObject",
    .tp_basicsize = sizeof(MyObject),
    .tp_doc = PyDoc_STR("My objects"),
    .tp_new = myobj_new,
    .tp_dealloc = (destructor)myobj_dealloc,
    .tp_repr = (reprfunc)myobj_repr,
};

老代码(尤其 CPython 自身)常见的冗长初始化器风格:

static PyTypeObject MyObject_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "mymod.MyObject",               /* tp_name */
    sizeof(MyObject),               /* tp_basicsize */
    0,                              /* tp_itemsize */
    (destructor)myobj_dealloc,      /* tp_dealloc */
    0,                              /* tp_vectorcall_offset */
    0,                              /* tp_getattr */
    0,                              /* tp_setattr */
    0,                              /* tp_as_async */
    (reprfunc)myobj_repr,           /* tp_repr */
    0,                              /* tp_as_number */
    0,                              /* tp_as_sequence */
    0,                              /* tp_as_mapping */
    0,                              /* tp_hash */
    0,                              /* tp_call */
    0,                              /* tp_str */
    0,                              /* tp_getattro */
    0,                              /* tp_setattro */
    0,                              /* tp_as_buffer */
    0,                              /* tp_flags */
    PyDoc_STR("My objects"),        /* tp_doc */
    0,                              /* tp_traverse */
    0,                              /* tp_clear */
    0,                              /* tp_richcompare */
    0,                              /* tp_weaklistoffset */
    0,                              /* tp_iter */
    0,                              /* tp_iternext */
    0,                              /* tp_methods */
    0,                              /* tp_members */
    0,                              /* tp_getset */
    0,                              /* tp_base */
    0,                              /* tp_dict */
    0,                              /* tp_descr_get */
    0,                              /* tp_descr_set */
    0,                              /* tp_dictoffset */
    0,                              /* tp_init */
    0,                              /* tp_alloc */
    myobj_new,                      /* tp_new */
};

支持弱引用、实例字典与哈希的类型(注意 Py_TPFLAGS_MANAGED_DICT / Py_TPFLAGS_MANAGED_WEAKREF 搭配 Py_TPFLAGS_HAVE_GC 使用):

typedef struct {
    PyObject_HEAD
    const char *data;
} MyObject;

static PyTypeObject MyObject_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "mymod.MyObject",
    .tp_basicsize = sizeof(MyObject),
    .tp_doc = PyDoc_STR("My objects"),
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE |
         Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_MANAGED_DICT |
         Py_TPFLAGS_MANAGED_WEAKREF,
    .tp_new = myobj_new,
    .tp_traverse = (traverseproc)myobj_traverse,
    .tp_clear = (inquiry)myobj_clear,
    .tp_alloc = PyType_GenericNew,
    .tp_dealloc = (destructor)myobj_dealloc,
    .tp_repr = (reprfunc)myobj_repr,
    .tp_hash = (hashfunc)myobj_hash,
    .tp_richcompare = PyBaseObject_Type.tp_richcompare,
};

str 的子类、不可再被继承、且不能通过调用创建实例(用 Py_TPFLAGS_DISALLOW_INSTANTIATION,实例由独立工厂函数创建;tp_base 因 C 初始化限制在模块 init 中赋 &PyUnicode_Type):

typedef struct {
    PyUnicodeObject raw;
    char *extra;
} MyStr;

static PyTypeObject MyStr_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "mymod.MyStr",
    .tp_basicsize = sizeof(MyStr),
    .tp_base = NULL,  // set to &PyUnicode_Type in module init
    .tp_doc = PyDoc_STR("my custom str"),
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION,
    .tp_repr = (reprfunc)myobj_repr,
};

最简单的定长与变长静态类型(变长示例中 tp_basicsize 减去末尾可变数组占位、tp_itemsize 为单元素大小,这是 PyObject_VAR_HEAD + 尾部数组布局的标准写法):

typedef struct {
    PyObject_HEAD
} MyObject;

static PyTypeObject MyObject_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "mymod.MyObject",
};

typedef struct {
    PyObject_VAR_HEAD
    const char *data[1];
} MyObject;

static PyTypeObject MyObject_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "mymod.MyObject",
    .tp_basicsize = sizeof(MyObject) - sizeof(char *),
    .tp_itemsize = sizeof(char *),
};

延伸阅读

编写自定义类型的总体工作流可以概括为:定义实例布局(PyObject_HEAD/PyObject_VAR_HEAD 开头)→ 声明 PyTypeObject 并填 tp_name/tp_basicsize 等必填项 → 按需实现 tp_new/tp_init/tp_dealloc/tp_free 与协议槽位 → 在模块初始化函数中修正 ob_typetp_base 等无法静态初始化的字段 → 调用 PyType_Ready(静态类型)或 PyType_FromSpec 家族(堆类型)完成继承槽位拷贝、MRO 计算与默认值填充。

登录后查看全文
热门项目推荐
相关项目推荐