Python基础 | 1.2PyTypeObject结构详解

引用自:https://docs.python.org/3/c-api/typeobj.html

(本篇博客是1.Python对象初探——>二、Python对象初探——>3.类型对象——>a.类型对象 _typeobject的扩展内容)

1.PyTypeObject结构源码展示

typedef 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; /* formerly known as tp_compare (Python 2)
                                    or tp_reserved (Python 3) */
    reprfunc tp_repr;

    /* Method suites for standard classes */

    PyNumberMethods *tp_as_number;
    PySequenceMethods *tp_as_sequence;
    PyMappingMethods *tp_as_mapping;

    /* More standard operations (here for binary compatibility) */

    hashfunc tp_hash;
    ternaryfunc tp_call;
    reprfunc tp_str;
    getattrofunc tp_getattro;
    setattrofunc tp_setattro;

    /* Functions to access object as input/output buffer */
    PyBufferProcs *tp_as_buffer;

    /* Flags to define presence of optional/expanded features */
    unsigned long tp_flags;

    const char *tp_doc; /* Documentation string */

    /* call function for all accessible objects */
    traverseproc tp_traverse;

    /* delete references to contained objects */
    inquiry tp_clear;

    /* rich comparisons */
    richcmpfunc tp_richcompare;

    /* weak reference enabler */
    Py_ssize_t tp_weaklistoffset;

    /* Iterators */
    getiterfunc tp_iter;
    iternextfunc tp_iternext;

    /* Attribute descriptor and subclassing stuff */
    struct PyMethodDef *tp_methods;
    struct PyMemberDef *tp_members;
    struct PyGetSetDef *tp_getset;
    struct _typeobject *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; /* Low-level free-memory routine */
    inquiry tp_is_gc; /* For PyObject_IS_GC */
    PyObject *tp_bases;
    PyObject *tp_mro; /* method resolution order */
    PyObject *tp_cache;
    PyObject *tp_subclasses;
    PyObject *tp_weaklist;
    destructor tp_del;

    /* Type attribute cache version tag. Added in version 2.6 */
    unsigned int tp_version_tag;

    destructor tp_finalize;

} PyTypeObject;

2.PyTypeObject结构源码逐行分析

下面我们将其分为X个部分进行分析

(1)第一部分:

a.代码展示:

typedef struct _typeobject {
    PyObject_VAR_HEAD
    const char *tp_name; /* For printing, in format "<module>.<name>" */
    Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */
    ...
    ...
} PyTypeObject;

b.分析:

1)PyObject_VAR_HEAD

PyObject_VAR_HEAD定义所有可变大小容器对象的初始段。 这些以“声明具有1个元素的数组”为结尾,但是已经分配了足够的空间,因此该数组实际上有空间容纳ob_size元素。 请注意,ob_size是元素计数,不一定是字节计数。

  • 首先我们可以看到,类型对象结构扩展了PyVarObject结构。下面是PyVarObject结构的再次介绍,关于PyObject、PyVarObject详细请点击
    • Py_ssize_t PyObject.ob_refcnt
      • 引用计数
    • PyTypeObject* PyObject.ob_type
      • 这是类型的类型,即它的元类型。 它由PyObject_HEAD_INIT宏的参数初始化,并且其值通常应为&PyType_Type。
    • Py_ssize_t PyVarObject.ob_size
      • 对于静态分配的类型对象,应将其初始化为零。 对于动态分配的类型对象,此字段具有特殊的内部含义。
      • ob_size字段用于动态类型(由type_new()创建,通常从类语句中调用)。请注意,PyType_Type(元类型)会初始化tp_itemsize,这意味着其实例(即类型对象)必须具有ob_size字段。【类型对象 是 PyType_Type(元类型) 的实例化。】

2)const char *tp_name

  • [通俗理解]类型名,tp_name,主要是Python内部以及调试的时候使用。
  • [官方解释]指向包含类型名称的NUL终止字符串的指针。
    • 对于可以作为模块全局变量访问的类型,字符串应为完整的模块名称,后跟一个点,然后是类型名称;对于内置类型,它应该只是类型名称。如果模块是软件包的子模块,则完整的软件包名称是完整模块名称的一部分。
      • 如:在包P的子包Q中的模块M中定义的名为T的类型应具有tp_name初始化程序“ P.Q.M.T”。
  • 该字段不能为NULL。它是PyTypeObject()中唯一需要的字段(潜在的tp_itemsize除外)。

3)Py_ssize_t tp_basicsize, tp_itemsize 计算类型实例的字节大小

  • tp_basicsize 与 tp_itemsize
    • tp_basicsize 可以认为是 “类型实例”的基本大小
    • tp_itemsize 可以认为是 元素大小
  • 关于定长对象和变长对象(这些类型对象都是“类型的类型”的实例)
    • 对于定长对象:
      • 定长对象的大小相同,由 tp_basicsize 直接给出。
      • 定长对象的 tp_itemsize 为 0.
    • 对于变长对象:
      • N表示为对象的长度,N的值保存在实例 ob_size 中。
      • 变长对象的的大小为:tp_basicsize + N * tp_itemsize。
    • 我的理解,如果错误,希望您的指出。
      • 对于定长对象,以int举例:
        import sys
        a = 100
        print("定长对象int(100)的大小:",sys.getsizeof(a))
        
        # 输出结果为
        定长对象int(100)的大小:28
        
        # 对于结果的分析:
        对于int类型(在数值大小小于 2^30 - 1 时),tp_basicsize = 28字节,tp_itemsize = 0
        
      • 对于变长对象,以string为例:
        import sys
        a = 'a'
        ab = 'ab'
        print("变长对象str('a')的大小:",sys.getsizeof(a))
        print("变长对象str('ab')的大小:",sys.getsizeof(ab))
        
        # 输出结果为:
        变长对象str('a')的大小:50
        变长对象str('ab')的大小:51
        
        # 对于结果的分析:
        对于string类型,tp_basicsize = 49;tp_itemsize = 1
        当 a = 'a'时,N(元素个数,存储在ob_size实例中)为1,因此,此时 sizeof('a') = 49 + 1 * 1 = 50;
        当 a = 'ab'时,N(元素个数,存储在ob_size实例中)为2,因此,此时 sizeof('ab') = 49 + 2 * 1 = 51;
        

(2)第二部分:

a.代码展示:

typedef struct _typeobject {
    ...
    /* Methods to implement standard operations */

    destructor tp_dealloc;
    Py_ssize_t tp_vectorcall_offset;
    getattrfunc tp_getattr;
    setattrfunc tp_setattr;
    PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2)
                                    or tp_reserved (Python 3) */
    reprfunc tp_repr;
    ...
} PyTypeObject;

b.分析:

1)destructor tp_dealloc

  • 指向实例析构函数的指针。除非该类型保证其实例永远不会被释放,否则必须定义此函数。
    void tp_dealloc(PyObject *self);
    
  • 当新引用计数为零时,Py_DECREF()和Py_XDECREF()宏将调用析构函数。此时,该实例仍然存在,但没有引用
    • 析构函数应释放实例所拥有的所有引用,释放实例所拥有的所有内存缓冲区(使用与用于分配缓冲区的分配函数相对应的释放函数),并调用类型的tp_free函数。
    • 如果类型不是子类型(没有设置Py_TPFLAGS_BASETYPE标志位),则可以直接调用对象解除分配器,而不是通过tp_free。对象分配器应该是用于分配实例的对象。
    • 如果使用PyObject_New()或PyObject_VarNew()分配实例,则通常为PyObject_Del();如果使用PyObject_GC_New()或PyObject_GC_NewVar()分配实例,则通常为PyObject_GC_Del()。
  • 最后,如果类型是堆分配的(Py_TPFLAGS_HEAPTYPE),则在调用类型解除分配器之后,解除分配器应减少其类型对象的引用计数。为了避免指针悬空,推荐的实现方法是:
    static void foo_dealloc(foo_object *self) {
        PyTypeObject *tp = Py_TYPE(self);
        // free references and buffers here
        tp->tp_free(self);
        Py_DECREF(tp);
    }
    

2)Py_ssize_t tp_vectorcall_offset

  • 每个实例函数的可选偏移量,该偏移量用于实现使用vectorcall协议调用对象的方法,这是更简单的tp_call的更有效的替代方法。
    • 仅当设置标志_Py_TPFLAGS_HAVE_VECTORCALL时才使用此字段。 如果是这样,则它必须是一个正整数,其中包含vectorcallfunc指针实例中的偏移量。
    • vectorcallfunc指针可以为零,在这种情况下,实例的行为就像未设置_Py_TPFLAGS_HAVE_VECTORCALL一样:调用实例将回退到tp_call。

3)getattrfunc tp_getattr

  • 指向get-attribute-string函数的可选指针。
    • 不建议使用此字段。 定义后,它应指向一个功能与tp_getattro函数相同的函数,但要使用C字符串而不是Python字符串对象来提供属性名称。

4)setattrfunc tp_setattr

  • 指向用于设置和删除属性的函数的可选指针。
    • 不建议使用此字段。 定义后,它应指向一个功能与tp_setattro函数相同的函数,但要使用C字符串而不是Python字符串对象来提供属性名称。

5)PyAsyncMethods *tp_as_async

  • 指向一个附加结构的指针,该结构包含仅与在C级实现等待和异步迭代器协议的对象有关的字段。
    • 3.5版的新功能:以前称为tp_compare和tp_reserved。

6)reprfunc tp_repr

  • 指向实现内置函数repr()的函数的可选指针。

(3)第三部分:

a.代码展示:

typedef struct _typeobject {
    ...
    /* Method suites for standard classes */

    PyNumberMethods *tp_as_number;
    PySequenceMethods *tp_as_sequence;
    PyMappingMethods *tp_as_mapping;
    ...
} PyTypeObject;

b.分析:

1)PyNumberMethods *tp_as_number

  • 指向另一个结构的指针,该结构包含仅与实现数字协议的对象相关的字段。 这些字段记录在“Number Object Structures”中。
    • [通俗理解]对象如果被认为是数值对象,那么这个对象应该支持哪些操作,每种操作的具体实现是什么样的。

2)PySequenceMethods *tp_as_sequence

  • 指向另一个结构的指针,该结构包含仅与实现序列协议的对象相关的字段。 这些字段记录在“Sequence Object Structures”中。
    • [通俗理解]如果一个对象被认为是一个序列对象,那么应该支持哪些操作。

3)PyMappingMethods *tp_as_mapping

  • 指向包含仅与实现映射协议的对象相关的字段的附加结构的指针。 这些字段记录在“Mapping Object Structures”中。
    • [通俗理解]如果一个对象被认为是映射对象,那么应该支持哪些操作。(映射对象:通过名字来引用值的数据结构。例如:字典)

(4)第四部分:

a.代码展示:

typedef struct _typeobject {
    ...
    /* More standard operations (here for binary compatibility) */

    hashfunc tp_hash;
    ternaryfunc tp_call;
    reprfunc tp_str;
    getattrofunc tp_getattro;
    setattrofunc tp_setattro;
    ...
} PyTypeObject;

b.分析:

1)hashfunc tp_hash

  • 指向实现内置函数hash()的函数的可选指针。
    • 签名与PyObject_Hash()相同:
      Py_hash_t tp_hash(PyObject *);
      
    • 值-1不应作为常规返回值返回;当在哈希值的计算过程中发生错误时,该函数应设置一个异常并返回-1。
    • 如果未设置此字段(并且未设置tp_richcompare),则尝试获取对象的哈希值会引发TypeError。这与将其设置为PyObject_HashNotImplemented()相同。
    • 可以将该字段显式设置为PyObject_HashNotImplemented(),以阻止哈希方法从父类型继承。在Python级别上,这被解释为等效于__hash__ = None,从而导致isinstance(o,collections.Hashable)正确返回False。请注意,相反情况也是如此-在python级别的类上设置__hash__ = None将导致tp_hash插槽设置为PyObject_HashNotImplemented()。

2)ternaryfunc tp_call

  • 指向实现调用对象的函数的可选指针。 如果对象不可调用,则应为NULL。
    • 签名与PyObject_Call()相同:
      PyObject *tp_call(PyObject *self, PyObject *args, PyObject *kwargs);
      

3)reprfunc tp_str

  • 指向实现内置操作str()的函数的可选指针。(请注意,str现在是一种类型,str()调用该类型的构造函数。此构造函数调用PyObject_Str()进行实际工作,而PyObject_Str()将调用此处理程序。)
    • 签名与PyObject_Str()相同:
      PyObject * tp_str(PyObject * self);
      
    • 该函数必须返回字符串或Unicode对象。 它应该是对象的“友好”字符串表示形式,因为它是print()函数将使用的表示形式。

4)getattrofunc tp_getattro

  • 指向get-attribute函数的可选指针。
    • 签名与PyObject_GetAttr()相同:
      PyObject * tp_getattro(PyObject * self,PyObject * attr);
      
    • 通常将此字段设置为PyObject_GenericGetAttr()很方便,该方法实现了查找对象属性的常规方法。

5)setattrofunc tp_setattro

  • 指向用于设置和删除属性的函数的可选指针。
    • 签名与PyObject_SetAttr()相同:
      PyObject * tp_setattro(PyObject * self,PyObject * attr,PyObject * value);
      
    • 另外,必须支持将值设置为NULL以删除属性。 通常将此字段设置为PyObject_GenericSetAttr()很方便,这实现了设置对象属性的常规方法。

(5)第五部分:

a.代码展示:

typedef struct _typeobject {
    ...
    /* Functions to access object as input/output buffer */
    PyBufferProcs *tp_as_buffer;

    /* Flags to define presence of optional/expanded features */
    unsigned long tp_flags;

    const char *tp_doc; /* Documentation string */

    /* call function for all accessible objects */
    traverseproc tp_traverse;

    /* delete references to contained objects */
    inquiry tp_clear;

    /* rich comparisons */
    richcmpfunc tp_richcompare;

    /* weak reference enabler */
    Py_ssize_t tp_weaklistoffset;

    /* Iterators */
    getiterfunc tp_iter;
    iternextfunc tp_iternext;
    ...
} PyTypeObject;

b.分析:

1)PyBufferProcs *tp_as_buffer

  • 指向包含仅与实现缓冲区接口的对象相关的字段的附加结构的指针。 这些字段记录在“缓冲区对象结构”中。

2)unsigned long tp_flags

  • 该字段是各种标志的位掩码。一些标志指示某些情况下的变体语义;其他字段用于指示类型对象(或通过tp_as_number,tp_as_sequence,tp_as_mapping和tp_as_buffer引用的扩展结构中的某些字段)过去一直不存在;如果清除了此类标志位,则必须禁止访问其保护的类型字段,而必须将其视为零或NULL值。
  • 该字段的继承很复杂。大多数标志位是单独继承的,即,如果基本类型设置了标志位,则子类型将继承此标志位。如果继承了扩展结构,则严格继承与扩展结构相关的标志位,即标志位的基本类型值与指向扩展结构的指针一起复制到子类型中。 Py_TPFLAGS_HAVE_GC标志位与tp_traverse和tp_clear字段一起继承,即如果Py_TPFLAGS_HAVE_GC标志位在子类型中被清除,并且子类型中的tp_traverse和tp_clear字段存在并且具有NULL值。
    // 标志位有:
    Py_TPFLAGS_HEAPTYPE
    Py_TPFLAGS_BASETYPE
    Py_TPFLAGS_READY
    Py_TPFLAGS_READYING
    Py_TPFLAGS_HAVE_GC
    Py_TPFLAGS_DEFAULT
    Py_TPFLAGS_METHOD_DESCRIPTOR
    Py_TPFLAGS_LONG_SUBCLASS
    Py_TPFLAGS_LIST_SUBCLASS
    Py_TPFLAGS_TUPLE_SUBCLASS
    Py_TPFLAGS_BYTES_SUBCLASS
    Py_TPFLAGS_UNICODE_SUBCLASS
    Py_TPFLAGS_DICT_SUBCLASS
    Py_TPFLAGS_BASE_EXC_SUBCLASS
    Py_TPFLAGS_TYPE_SUBCLASS
    Py_TPFLAGS_HAVE_FINALIZE
    _Py_TPFLAGS_HAVE_VECTORCALL
    

3)const char *tp_doc

  • 指向NUL终止的C字符串的可选指针,为该类型对象提供docstring。 这在类型和类型实例上作为__doc__属性公开。

4)traverseproc tp_traverse

  • 指向垃圾收集器遍历函数的可选指针。 仅在Py_TPFLAGS_HAVE_GC标志位置1时使用。
    • 标志位置1时使用。 签名是:
      int tp_traverse(PyObject * self,visitproc访问,void * arg);
      
    • 垃圾回收器使用tp_traverse指针检测参考周期。 tp_traverse函数的典型实现只是在实例的每个实例成员(它们是实例拥有的Python对象)上调用Py_VISIT()。

5)inquiry tp_clear

  • 指向垃圾回收器的清除函数的可选指针。仅在Py_TPFLAGS_HAVE_GC标志位置1时使用。
    • 签名是:
      int tp_clear(PyObject *);
      
    • tp_clear成员函数用于中断垃圾收集器检测到的循环垃圾中的参考周期。总之,系统中的所有tp_clear函数必须结合起来才能中断所有参考周期。这是微妙的,如果有任何疑问,请提供tp_clear函数。例如,元组类型不实现tp_clear函数,因为有可能证明没有参考周期可以完全由元组组成。因此,其他类型的tp_clear函数必须足以中断包含元组的任何循环。这不是立即显而易见的,很少有充分的理由避免实施tp_clear。
    • tp_clear的实现应删除实例对其可能是Python对象的成员的引用,并将其指向那些成员的指针设置为NULL。
    • 应该使用Py_CLEAR()宏,因为清除引用非常困难:在将包含对象的指针设置为NULL之前,不得减少对包含对象的引用。这是因为减少引用计数可能会导致所包含的对象成为垃圾,从而触发一连串的回收活动,该活动可能包括调用任意Python代码(由于与所包含的对象相关联的终结器或weakref回调)。如果此类代码有可能再次引用self,那么在那时指向所包含对象的指针为NULL十分重要,这样self就知道不再可以使用所包含的对象。 Py_CLEAR()宏以安全顺序执行操作。
    • 由于tp_clear函数的目的是打破参考周期,因此不必清除无法参与参考周期的包含对象,例如Python字符串或Python整数。另一方面,清除所有包含的Python对象并编写该类型的tp_dealloc函数以调用tp_clear可能会很方便。

6)richcmpfunc tp_richcompare

  • 从函数返回Py_True或Py_False,具体取决于比较结果。 VAL_A和VAL_B必须可由C比较运算符排序(例如,它们可以是C int或float)。 第三个参数指定请求的操作,与PyObject_RichCompare()相同。
  • 返回值的参考计数已正确增加。
  • 出错时,设置一个异常并从函数返回NULL。

7)Py_ssize_t tp_weaklistoffset

  • 如果该类型的实例是弱引用的,则此字段大于零,并且包含弱引用列表头的实例结构中的偏移量(如果存在,则忽略GC头);此偏移量由PyObject_ClearWeakRefs()和PyWeakref _ *()函数使用。实例结构需要包括一个PyObject *类型的字段,该字段被初始化为NULL。
  • 不要将此字段与tp_weaklist混淆。这是对类型对象本身的弱引用的列表头。

8)getiterfunc tp_iter

  • 指向函数的可选指针,该函数返回对象的迭代器。 它的存在通常表示此类型的实例是可迭代的(尽管如果没有此功能,序列可能是可迭代的)。
    • 此函数与PyObject_GetIter()具有相同的签名:
      PyObject *tp_iter(PyObject *self);
      

9)iternextfunc tp_iternext

  • 指向函数的可选指针,该函数返回迭代器中的下一项。
    • 签名是:
      PyObject * tp_iternext(PyObject * self);
      
    • 当迭代器用尽时,它必须返回NULL;否则,它必须返回NULL。 可能会或可能不会设置StopIteration异常。 当发生另一个错误时,它也必须返回NULL。 它的存在表明该类型的实例是迭代器。
    • 迭代器类型还应该定义tp_iter函数,并且该函数应返回迭代器实例本身(而不是新的迭代器实例)。
    • 此函数具有与PyIter_Next()相同的签名。

(6)第六部分:

a.代码展示:

typedef struct _typeobject {
    ...
    /* Attribute descriptor and subclassing stuff */
    struct PyMethodDef *tp_methods;
    struct PyMemberDef *tp_members;
    struct PyGetSetDef *tp_getset;
    struct _typeobject *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; /* Low-level free-memory routine */
    inquiry tp_is_gc; /* For PyObject_IS_GC */
    PyObject *tp_bases;
    PyObject *tp_mro; /* method resolution order */
    PyObject *tp_cache;
    PyObject *tp_subclasses;
    PyObject *tp_weaklist;
    destructor tp_del;
    ...
} PyTypeObject;

b.分析:

1)struct PyMethodDef *tp_methods

  • 指向以NULL结尾的静态PyMethodDef结构数组的可选指针,声明此类型的常规方法。
    • 对于数组中的每个条目,都会将一个条目添加到包含方法描述符的类型的字典中(请参见下面的tp_dict)。

2)struct PyMemberDef *tp_members

  • 指向以NULL终止的静态PyMemberDef结构的数组的可选指针,声明此类型实例的常规数据成员(字段或插槽)。
    • 对于数组中的每个条目,都会将一个条目添加到包含成员描述符的类型的字典中(请参见下面的tp_dict)。

3)struct PyGetSetDef *tp_getset

  • 指向以NULL结尾的静态PyGetSetDef结构数组的可选指针,声明此类型实例的计算属性。
    • 对于数组中的每个条目,都会将一个条目添加到包含getset描述符的类型的字典中(请参见下面的tp_dict)。

4)struct _typeobject *tp_base

  • 指向从其继承类型属性的基本类型的可选指针。在此级别上,仅支持单继承。多重继承要求通过调用元类型来动态创建类型对象。
    • 注意插槽初始化须遵守初始化全局变量的规则。 C99要求初始化器为“地址常数”。诸如PyType_GenericNew()之类的函数指示符(通过隐式转换为指针)是有效的C99地址常量。
      但是,不需要使用应用于PyBaseObject_Type()之类的非静态变量的一元“&”运算符即可生成地址常数。编译器可能支持(gcc支持),MSVC不支持。两种编译器在此特定行为上均严格遵循标准。
    • 因此,应该在扩展模块的init函数中设置tp_base。

5)PyObject *tp_dict

  • 类型的字典通过PyType_Ready()存储在此处。
    • 在调用PyType_Ready之前,通常应将此字段初始化为NULL; 它也可以初始化为包含该类型的初始属性的字典。 一旦PyType_Ready()初始化了类型,该类型的额外属性只有在它们不对应于重载操作(例如__add __())时才可以添加到该字典中。

6)descrgetfunc tp_descr_get

  • 指向“descriptor get”功能的可选指针。
    • 函数签名为:
      PyObject * tp_descr_get(PyObject *self, PyObject *obj, PyObject *type);
      

7)descrsetfunc tp_descr_set

  • 指向用于设置和删除描述符值的函数的可选指针。
    • 函数签名为:
      int tp_descr_set(PyObject *self, PyObject *obj, PyObject *value);
      
    • value参数设置为NULL以删除该值。

8)Py_ssize_t tp_dictoffset

  • 如果此类型的实例具有包含实例变量的字典,则此字段为非零值,并且包含实例变量字典类型的实例中的偏移量;否则为0。此偏移量由PyObject_GenericGetAttr()使用。
  • 不要将此字段与tp_dict混淆;那是类型对象本身的属性的字典。
  • 如果此字段的值大于零,则它指定从实例结构开始的偏移量。如果该值小于零,则指定距实例结构末端的偏移量。负偏移量使用起来更昂贵,并且仅当实例结构包含可变长度部分时才应使用。例如,这用于将实例变量字典添加到str或tuple的子类型。请注意,即使在基本对象布局中没有包含字典的情况下,tp_basicsize字段也应说明添加到末尾的字典。在指针大小为4字节的系统上,应将tp_dictoffset设置为-4,以指示字典位于结构的最末端。
  • 实例中的实字典偏移量可以根据负tp_dictoffset计算如下:
    dictoffset = tp_basicsize + abs(ob_size)*tp_itemsize + tp_dictoffset
    if dictoffset is not aligned on sizeof(void*):
        round up to sizeof(void*)
    
    • 其中,tp_basicsize,tp_itemsize和tp_dictoffset从类型对象中获取,而ob_size从实例中获取。使用绝对值是因为int使用ob_size的符号来存储数字的符号。 (永远不需要您自己进行此计算;它是由_PyObject_GetDictPtr()为您完成的。)

9)initproc tp_init

  • 实例初始化函数的可选指针。
  • 此函数对应于类的__init __()方法。像__init __()一样,可以在不调用__init __()的情况下创建实例,并且可以通过再次调用其__init __()方法来重新初始化实例。
    • 函数签名为:
      int tp_init(PyObject *self, PyObject *args, PyObject *kwds);
      
    • self参数是要初始化的实例; args和kwds参数表示对__init __()的调用的位置参数和关键字参数。
    • 当类型的tp_new函数返回该类型的实例后,通过调用其类型正常创建实例时,会调用tp_init函数(如果不为NULL)。如果tp_new函数返回的不是原始类型的子类型的某个其他类型的实例,则不会调用tp_init函数;否则,它将被调用。如果tp_new返回原始类型的子类型的实例,则调用该子类型的tp_init。
    • 成功则返回0,-1则返回错误。

10)allocfunc tp_alloc

  • 指向实例分配功能的可选指针。
    • 函数签名为:
      PyObject * tp_alloc(PyTypeObject * self,Py_ssize_t nitems);
      

11)newfunc tp_new

  • 指向实例创建函数的可选指针。
    • 函数签名为:
      PyObject *tp_new(PyTypeObject *subtype, PyObject *args, PyObject *kwds);
      
    • subtype参数是要创建的对象的类型。 args和kwds参数表示对该类型的调用的位置参数和关键字参数。 请注意,子类型不必等于其tp_new函数被调用的类型。 它可能是该类型的子类型(但不是无关类型)。
    • tp_new函数应调用subtype-> tp_alloc(subtype,nitems)为对象分配空间,然后仅在绝对必要的情况下进行更多的初始化。 可以安全地忽略或重复进行的初始化应放在tp_init处理程序中。 一个好的经验法则是,对于不可变类型,所有初始化都应在tp_new中进行,而对于可变类型,大多数初始化应推迟到tp_init中进行。

12)freefunc tp_free

  • 实例释放函数的可选指针。
    • 它的签名是:
      void tp_free(void *self);
      
    • 与此签名兼容的初始化器为PyObject_Free()。

13)inquiry tp_is_gc

  • 指向垃圾回收器调用的函数的可选指针。
  • 垃圾收集器需要知道特定对象是否可收集。 通常,查看对象类型的tp_flags字段并检查Py_TPFLAGS_HAVE_GC标志位就足够了。 但是某些类型混合了静态和动态分配的实例,并且静态分配的实例是不可收集的。 这些类型应定义此功能。 对于可收集的实例,它应该返回1;对于不可收集的实例,它应该返回0。
    • 签名是:
      int tp_is_gc(PyObject * self);
      
      • (这种情况的唯一示例就是类型本身。元类型PyType_Type定义了此函数,以区分静态分配类型和动态分配类型。)

14)PyObject *tp_bases

  • 基本类型的元组。
    • 这是为由类语句创建的类型设置的。 对于静态定义的类型,它应该为NULL。

15)PyObject *tp_mro

  • 在方法解析顺序中,元组包含扩展的基本类型集,从基本类型本身开始到对象结束。

16)PyObject *tp_cache

  • 没用过。 仅供内部使用。

17)PyObject *tp_subclasses

  • 对子类的弱引用列表。 仅供内部使用。

18)PyObject *tp_weaklist

  • 弱引用列表头,用于对此类型对象的弱引用。 不继承。 仅供内部使用。

19)destructor tp_del

  • 不建议使用此字段。 请改用tp_finalize。

(7)第七部分:

a.代码展示:

typedef struct _typeobject {
    ...
    ...
    /* Type attribute cache version tag. Added in version 2.6 */
    unsigned int tp_version_tag;

    destructor tp_finalize;
} PyTypeObject;

b.分析:

1)unsigned int tp_version_tag

  • 用于索引方法缓存。 仅供内部使用。

2)destructor tp_finalize

  • 实例完成函数的可选指针。
    • 它的签名是:
      void tp_finalize(PyObject *self);
      
    • 如果设置了tp_finalize,则在完成实例时,解释器将调用一次。从垃圾回收器(如果实例是一个隔离的引用周期的一部分)中调用它,或者在对象被释放之前调用它。无论哪种方式,都可以确保在尝试中断参考周期之前调用它,以确保它找到处于健全状态的对象。
    • tp_finalize不应改变当前的异常状态;因此,推荐的写平凡终结器的方法是:
      static void
      local_finalize(PyObject *self)
      {
          PyObject *error_type, *error_value, *error_traceback;
      
          /* Save the current exception, if any. */
          PyErr_Fetch(&error_type, &error_value, &error_traceback);
      
          /* ... */
      
          /* Restore the saved exception. */
          PyErr_Restore(error_type, error_value, error_traceback);
      }
      
    • 为了考虑到该字段(甚至通过继承),还必须将Py_TPFLAGS_HAVE_FINALIZE标志位设置为1。

posted on 2020-06-12 20:44  wangxx06  阅读(853)  评论(0编辑  收藏  举报

导航