静态类型

在传统上,在 C 代码中定义的类型都是 静态的,也就是说,PyTypeObject 结构体在代码中直接定义并使用 PyType_Ready() 来初始化。

这就导致了与在 Python 中定义的类型相关联的类型限制:

  • 静态类型只能拥有一个基类;换句话说,他们不能使用多重继承。

  • 静态类型对象(但并非它们的实例)是不可变对象。 不可能在 Python 中添加或修改类型对象的属性。

  • 静态类型对象是跨 子解释器 共享的,因此它们不应包括任何子解释器专属的状态。

此外,由于 PyTypeObject 只是作为不透明结构的 受限 API 的一部分,因此任何使用静态类型的扩展模块都必须针对特定的 Python 次版本进行编译。

堆类型

一种 静态类型的 替代物是 堆分配类型,或者简称 堆类型,它与使用 Python 的 class 语句创建的类紧密对应。 堆类型设置了 Py_TPFLAGS_HEAPTYPE 旗标。

这是通过填充 PyType_Spec 结构体并调用 PyType_FromSpec(), PyType_FromSpecWithBases(), PyType_FromModuleAndSpec()PyType_FromMetaclass() 来实现的。

数字对象结构体

  • type PyNumberMethods
  • 该结构体持有指向被对象用来实现数字协议的函数的指针。 每个函数都被 数字协议 一节中记录的对应名称的函数所使用。

结构体定义如下:

  1. 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;
  2. } PyNumberMethods;

备注

双目和三目函数必须检查其所有操作数的类型,并实现必要的转换(至少有一个操作数是所定义类型的实例)。 如果没有为所给出的操作数定义操作,则双目和三目函数必须返回 Py_NotImplemented,如果发生了其他错误则它们必须返回 NULL 并设置一个异常。

备注

nb_reserved 字段应当始终为 NULL。 在之前版本中其名称为 nb_long,并在 Python 3.0.1 中改名。

映射对象结构体

  • type PyMappingMethods
  • 该结构体持有指向对象用于实现映射协议的函数的指针。 它有三个成员:

序列对象结构体

  • type PySequenceMethods
  • 该结构体持有指向对象用于实现序列协议的函数的指针。
  • 此函数被 PySequence_Concat() 所使用并具有相同的签名。 在尝试通过 nb_add 槽位执行数值相加之后它还会被用于 + 运算符。
  • 此函数被 PySequence_Repeat() 所使用并具有相同的签名。 在尝试通过 nb_multiply 槽位执行数值相乘之后它还会被用于 * 运算符。

负索引号是按如下方式处理的:如果 sq_length 槽位已被填充,它将被调用并使用序列长度来计算出正索引号并传给 sq_item。 如果 sq_lengthNULL,索引号将原样传给此函数。

  • 该函数可供 PySequence_Contains() 使用并具有相同的签名。 此槽位可以保持为 NULL,在此情况下 PySequence_Contains() 只需遍历该序列直到找到一个匹配。
  • 此函数被 PySequence_InPlaceConcat() 所使用并具有相同的签名。 它应当修改它的第一个操作数,并将其返回。 该槽位可以保持为 NULL,在此情况下 PySequence_InPlaceConcat() 将回退到 PySequence_Concat()。 在尝试通过 nb_inplace_add 槽位执行数字原地相加之后它还会被用于增强赋值运算符 +=
  • 此函数被 PySequence_InPlaceRepeat() 所使用并具有相同的签名。 它应当修改它的第一个操作数,并将其返回。 该槽位可以保持为 NULL,在此情况下 PySequence_InPlaceRepeat() 将回退到 PySequence_Repeat()。 在尝试通过 nb_inplace_multiply 槽位执行数字原地相乘之后它还会被用于增强赋值运算符 *=

缓冲区对象结构体

  • type PyBufferProcs
  • 此结构体持有指向 缓冲区协议 所需要的函数的指针。 该协议定义了导出方对象要如何向消费方对象暴露其内部数据。
  • 此函数的签名为:
  1. int (PyObject *exporter, Py_buffer *view, int flags);

处理发给 exporter 的请求来填充 flags 所指定的 view。 除第 (3) 点外,此函数的实现必须执行以下步骤:

  • 检查请求是否能被满足。 如果不能,则会引发 BufferError,将 view->obj 设为 NULL 并返回 -1

  • 填充请求的字段。

  • 递增用于保存导出次数的内部计数器。

  • 将 view->obj 设为 exporter 并递增 view->obj。

  • 返回 0

如果 exporter 是缓冲区提供方的链式或树型结构的一部分,则可以使用两种主要方案:

  • 重导出:树型结构的每个成员作为导出对象并将 view->obj 设为对其自身的新引用。

  • 重定向:缓冲区请求将被重定向到树型结构的根对象。 在此,view->obj 将为对根对象的新引用。

view 中每个字段的描述参见 缓冲区结构体 一节,导出方对于特定请求应当如何反应参见 缓冲区请求类型 一节。

所有在 Py_buffer 结构体中被指向的内存都属于导出方并必须保持有效直到不再有任何消费方。 format, shape, strides, suboffsetsinternal 对于消费方来说是只读的。

PyBuffer_FillInfo() 提供了一种暴露简单字节缓冲区同时正确处理地所有请求类型的简便方式。

PyObject_GetBuffer() 是针对包装此函数的消费方的接口。

  • 此函数的签名为:
  1. void (PyObject *exporter, Py_buffer *view);

处理释放缓冲区资源的请求。 如果不需要释放任何资源,则 PyBufferProcs.bf_releasebuffer 可以为 NULL。 在其他情况下,此函数的标准实现将执行以下的可选步骤:

  • 递减用于保存导出次数的内部计数器。

  • 如果计数器为 0,则释放所有关联到 view 的内存。

导出方必须使用 internal 字段来记录缓冲区专属的资源。 该字段将确保恒定,而消费方则可能将原始缓冲区作为 view 参数传入。

此函数不可递减 view->obj,因为这是在 PyBuffer_Release() 中自动完成的(此方案适用于打破循环引用)。

PyBuffer_Release() 是针对包装此函数的消费方的接口。

异步对象结构体

Added in version 3.5.

  • type PyAsyncMethods

结构体定义如下:

  1. typedef struct { unaryfunc am_await; unaryfunc am_aiter; unaryfunc am_anext; sendfunc am_send;
  2. } PyAsyncMethods;
  • 此函数的签名为:
  1. PyObject *am_await(PyObject *self);

返回的对象必须为 iterator,即对其执行 PyIter_Check() 必须返回 1

如果一个对象不是 awaitable 则此槽位可被设为 NULL

  • 此函数的签名为:
  1. PyObject *am_aiter(PyObject *self);

必须返回一个 asynchronous iterator 对象。 请参阅 __anext__() 了解详情。

如果一个对象没有实现异步迭代协议则此槽位可被设为 NULL

  • 此函数的签名为:
  1. PyObject *am_anext(PyObject *self);

必须返回一个 awaitable 对象。 请参阅 __anext__() 了解详情。 此槽位可被设为 NULL

  • 此函数的签名为:
  1. PySendResult am_send(PyObject *self, PyObject *arg, PyObject **result);

请参阅 PyIter_Send() 了解详情。 此槽位可被设为 NULL

Added in version 3.10.

槽位类型 typedef

  • 属于 稳定 ABI. 此函数的设计目标是将内存分配与内存初始化进行分离。 它应当返回一个指向足够容纳实例长度,适当对齐,并初始化为零的内存块的指针,但将 ob_refcnt 设为 1 并将 ob_type 设为 type 参数。 如果类型的 tp_itemsize 为非零值,则对象的 ob_size 字段应当被初始化为 nitems 而分配内存块的长度应为 tp_basicsize + nitems*tp_itemsize,并舍入到 sizeof(void*) 的倍数;在其他情况下,nitems 将不会被使用而内存块的长度应为 tp_basicsize

此函数不应执行任何其他实例初始化操作,即使是分配额外内存也不应执行;那应当由 tp_new 来完成。

  • typedef void (freefunc)(void)
  • 属于 稳定 ABI. 返回对象的指定属性的值。
  • 属于 稳定 ABI. 为对象设置指定属性的值。 将 value 参数设为 NULL 将删除该属性。
  • 属于 稳定 ABI. 返回对象的指定属性的值。

参见 tp_getattro

  • 属于 稳定 ABI. 为对象设置指定属性的值。 将 value 参数设为 NULL 将删除该属性。

参见 tp_setattro

例子

下面是一些 Python 类型定义的简单示例。 其中包括你可能会遇到的通常用法。 有些演示了令人困惑的边际情况。 要获取更多示例、实践信息以及教程,请参阅 自定义扩展类型:教程定义扩展类型:已分类主题

一个基本的 静态类型:

  1. typedef struct { PyObject_HEAD const char *data;
  2. } MyObject;
  3.  
  4. 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,
  5. };

你可能还会看到带有更繁琐的初始化器的较旧代码(特别是在 CPython 代码库中):

  1. 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 */
  2. };

一个支持弱引用、实例字典和哈希运算的类型:

  1. typedef struct { PyObject_HEAD const char *data;
  2. } MyObject;
  3.  
  4. 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,
  5. };

一个不能被子类化且不能被调用以使用 Py_TPFLAGS_DISALLOW_INSTANTIATION 旗标创建实例(例如使用单独的工厂函数)的 str 子类:

  1. typedef struct { PyUnicodeObject raw; char *extra;
  2. } MyStr;
  3.  
  4. 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,
  5. };

最简单的固定长度实例 静态类型:

  1. typedef struct { PyObject_HEAD
  2. } MyObject;
  3.  
  4. static PyTypeObject MyObject_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "mymod.MyObject",
  5. };

最简单的具有可变长度实例的 静态类型:

  1. typedef struct { PyObject_VAR_HEAD const char *data[1];
  2. } MyObject;
  3.  
  4. static PyTypeObject MyObject_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "mymod.MyObject", .tp_basicsize = sizeof(MyObject) - sizeof(char *), .tp_itemsize = sizeof(char *),
  5. };