2. 定义新类型

如上一章所述,Python 允许扩展模块的编写者定义可以从 Python 代码操作的新类型,就像核心 Python 中的字符串和列表一样。

这并不难。所有扩展类型的代码都遵循一种模式,但是在开始之前,您需要了解一些详细信息。

Note

在 Python 2.2 中,定义新类型的方式发生了巨大变化(并且更好)。本文档记录了如何为 Python 2.2 及更高版本定义新类型。如果需要支持旧版本的 Python,则需要参考本文档的旧版本

2.1. 基础

Python 运行时将所有 Python 对象视为PyObject*类型的变量。 PyObject不是一个非常宏伟的对象-它仅包含引用计数和指向该对象的“类型对象”的指针。这是行动所在;类型对象确定在某个对象上查找某个属性或该属性与另一个对象相乘时将调用哪些(C)函数。这些 C 函数称为“类型方法”。

因此,如果要定义新的对象类型,则需要创建一个新的对象类型。

这种事情只能pass示例来解释,因此这是一个最小但完整的模块,它定义了一种新类型:

#include <Python.h>

typedef struct {
    PyObject_HEAD
    /* Type-specific fields go here. */
} noddy_NoddyObject;

static PyTypeObject noddy_NoddyType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "noddy.Noddy",             /* tp_name */
    sizeof(noddy_NoddyObject), /* tp_basicsize */
    0,                         /* tp_itemsize */
    0,                         /* tp_dealloc */
    0,                         /* tp_print */
    0,                         /* tp_getattr */
    0,                         /* tp_setattr */
    0,                         /* tp_compare */
    0,                         /* 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 */
    Py_TPFLAGS_DEFAULT,        /* tp_flags */
    "Noddy objects",           /* tp_doc */
};

static PyMethodDef noddy_methods[] = {
    {NULL}  /* Sentinel */
};

#ifndef PyMODINIT_FUNC	/* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif
PyMODINIT_FUNC
initnoddy(void) 
{
    PyObject* m;

    noddy_NoddyType.tp_new = PyType_GenericNew;
    if (PyType_Ready(&noddy_NoddyType) < 0)
        return;

    m = Py_InitModule3("noddy", noddy_methods,
                       "Example module that creates an extension type.");

    Py_INCREF(&noddy_NoddyType);
    PyModule_AddObject(m, "Noddy", (PyObject *)&noddy_NoddyType);
}

现在可以同时进行很多工作,但是希望从上一章开始就可以熟悉一下。

将是新的第一位是:

typedef struct {
    PyObject_HEAD
} noddy_NoddyObject;

这就是 Noddy 对象将包含的内容—在这种情况下,仅是每个 Python 对象包含的内容,即引用计数和指向类型对象的指针。这些是PyObject_HEAD宏带来的字段。该宏的原因是标准化布局并在调试版本中启用特殊的调试字段。请注意,PyObject_HEAD宏后没有分号。宏定义中包含一个。提防意外添加一个;从习惯上很容易做到,您的编译器可能不会抱怨,但是其他人可能会抱怨! (在 Windows 上,已知 MSVC 将此称为错误并拒绝编译代码.)

相比之下,让我们看一下标准 Python 整数的相应定义:

typedef struct {
    PyObject_HEAD
    long ob_ival;
} PyIntObject;

continue,我们来探讨紧要关头-类型对象。

static PyTypeObject noddy_NoddyType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "noddy.Noddy",             /* tp_name */
    sizeof(noddy_NoddyObject), /* tp_basicsize */
    0,                         /* tp_itemsize */
    0,                         /* tp_dealloc */
    0,                         /* tp_print */
    0,                         /* tp_getattr */
    0,                         /* tp_setattr */
    0,                         /* tp_compare */
    0,                         /* 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 */
    Py_TPFLAGS_DEFAULT,        /* tp_flags */
    "Noddy objects",           /* tp_doc */
};

现在,如果您在object.h中查找PyTypeObject的定义,您会发现它比上面的定义还有更多字段。其余字段将由 C 编译器填充为零,除非您需要,否则通常不明确指定它们。

这是如此重要,以至于我们将进一步将其分开:

PyVarObject_HEAD_INIT(NULL, 0)

这条线有点疣。我们要写的是:

PyVarObject_HEAD_INIT(&PyType_Type, 0)

因为类型对象的类型是“类型”,但这并不严格符合 C 语言,因此有些编译器会抱怨。幸运的是,此成员将由PyType_Ready()替我们填写。

"noddy.Noddy",              /* tp_name */

我们类型的名称。这将显示在对象的默认文本表示形式中,并出现在一些错误消息中,例如:

>>> "" + noddy.new_noddy()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: cannot add type "noddy.Noddy" to string

请注意,该名称是点名,包括模块名称和模块内类型的名称。在这种情况下,模块为noddy,类型为Noddy,因此我们将类型名称设置为noddy.Noddy。使用不带点的名称的副作用是 pydoc 文档工具不会在模块文档中列出新类型。

sizeof(noddy_NoddyObject),  /* tp_basicsize */

这样一来,Python 在调用PyObject_New()时便知道要分配多少内存。

Note

如果您希望您的类型可以从 Python 继承而来,并且您的类型与其基本类型具有相同的tp_basicsize,则可能存在多重继承问题。您类型的 Python 子类将必须首先在其bases中列出您的类型,否则它将在没有错误的情况下无法调用您类型的new()方法。您可以pass确保您的类型tp_basicsize的值大于其基本类型的值来避免此问题。在大多数情况下,无论如何这都是正确的,因为您的基本类型将为object,否则您将向基本类型添加数据成员,因此会增加其大小。

0,                          /* tp_itemsize */

这与长度可变的对象(如列表和字符串)有关。现在忽略此。

跳过一些我们不提供的类型方法,我们将类标志设置为Py_TPFLAGS_DEFAULT

Py_TPFLAGS_DEFAULT,        /* tp_flags */

所有类型都应在其标志中包含此常量。它启用了当前版本的 Python 定义的所有成员。

我们为tp_doc中的类型提供一个文档字符串。

"Noddy objects",           /* tp_doc */

现在我们进入类型方法,这些方法使您的对象与其他对象有所不同。我们不会在此版本的模块中实现任何这些Function。稍后我们将扩展此示例,使其具有更多有趣的行为。

现在,我们要做的就是创建新的Noddy对象。要启用对象创建,我们必须提供tp_new实现。在这种情况下,我们只能使用 API 函数PyType_GenericNew()提供的默认实现。我们只想将它分配给tp_new插槽,但是出于可移植性的考虑,我们不能,在某些平台或编译器上,我们无法使用在另一个 C 模块中定义的函数静态地初始化结构成员,因此, ,我们将在调用PyType_Ready()之前在模块初始化函数中分配tp_new插槽:

noddy_NoddyType.tp_new = PyType_GenericNew;
if (PyType_Ready(&noddy_NoddyType) < 0)
    return;

所有其他类型方法都是* NULL *,因此我们将在以后进行介绍-这将在后面的部分中介绍!

除了initnoddy()中的一些代码,文件中的其他所有内容都应该熟悉:

if (PyType_Ready(&noddy_NoddyType) < 0)
    return;

这将初始化Noddy类型,并在许多成员中归档,包括我们最初设置为* NULL *的ob_type

PyModule_AddObject(m, "Noddy", (PyObject *)&noddy_NoddyType);

这会将类型添加到模块字典中。这使我们可以pass调用Noddy类来创建Noddy实例:

>>> import noddy
>>> mynoddy = noddy.Noddy()

而已!剩下的就是构建它。将上面的代码放在名为noddy.c的文件中,

from distutils.core import setup, Extension
setup(name="noddy", version="1.0",
      ext_modules=[Extension("noddy", ["noddy.c"])])

在名为setup.py的文件中;然后 Importing

$ python setup.py build

在 shell 上应在子目录中产生文件noddy.so;转到该目录并启动 Python-您应该能够import noddy并使用 Noddy 对象。

那不是那么难,不是吗?

当然,当前的 Noddy 类型非常有趣。它没有数据,什么也没做。它甚至不能被子类化。

2.1.1. 向基本示例添加数据和方法

让我们扩展基本示例以添加一些数据和方法。让我们还将类型用作 Base Class。我们将创建一个新模块noddy2,其中添加了以下Function:

#include <Python.h>
#include "structmember.h"

typedef struct {
    PyObject_HEAD
    PyObject *first; /* first name */
    PyObject *last;  /* last name */
    int number;
} Noddy;

static void
Noddy_dealloc(Noddy* self)
{
    Py_XDECREF(self->first);
    Py_XDECREF(self->last);
    Py_TYPE(self)->tp_free((PyObject*)self);
}

static PyObject *
Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    Noddy *self;

    self = (Noddy *)type->tp_alloc(type, 0);
    if (self != NULL) {
        self->first = PyString_FromString("");
        if (self->first == NULL) {
            Py_DECREF(self);
            return NULL;
        }

        self->last = PyString_FromString("");
        if (self->last == NULL) {
            Py_DECREF(self);
            return NULL;
        }

        self->number = 0;
    }

    return (PyObject *)self;
}

static int
Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
{
    PyObject *first=NULL, *last=NULL, *tmp;

    static char *kwlist[] = {"first", "last", "number", NULL};

    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;
}

static PyMemberDef Noddy_members[] = {
    {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,
     "first name"},
    {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,
     "last name"},
    {"number", T_INT, offsetof(Noddy, number), 0,
     "noddy number"},
    {NULL}  /* Sentinel */
};

static PyObject *
Noddy_name(Noddy* self)
{
    static PyObject *format = NULL;
    PyObject *args, *result;

    if (format == NULL) {
        format = PyString_FromString("%s %s");
        if (format == NULL)
            return NULL;
    }

    if (self->first == NULL) {
        PyErr_SetString(PyExc_AttributeError, "first");
        return NULL;
    }

    if (self->last == NULL) {
        PyErr_SetString(PyExc_AttributeError, "last");
        return NULL;
    }

    args = Py_BuildValue("OO", self->first, self->last);
    if (args == NULL)
        return NULL;

    result = PyString_Format(format, args);
    Py_DECREF(args);

    return result;
}

static PyMethodDef Noddy_methods[] = {
    {"name", (PyCFunction)Noddy_name, METH_NOARGS,
     "Return the name, combining the first and last name"
    },
    {NULL}  /* Sentinel */
};

static PyTypeObject NoddyType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "noddy.Noddy",             /* tp_name */
    sizeof(Noddy),             /* tp_basicsize */
    0,                         /* tp_itemsize */
    (destructor)Noddy_dealloc, /* tp_dealloc */
    0,                         /* tp_print */
    0,                         /* tp_getattr */
    0,                         /* tp_setattr */
    0,                         /* tp_compare */
    0,                         /* 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 */
    Py_TPFLAGS_DEFAULT |
        Py_TPFLAGS_BASETYPE,   /* tp_flags */
    "Noddy objects",           /* tp_doc */
    0,                         /* tp_traverse */
    0,                         /* tp_clear */
    0,                         /* tp_richcompare */
    0,                         /* tp_weaklistoffset */
    0,                         /* tp_iter */
    0,                         /* tp_iternext */
    Noddy_methods,             /* tp_methods */
    Noddy_members,             /* tp_members */
    0,                         /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    (initproc)Noddy_init,      /* tp_init */
    0,                         /* tp_alloc */
    Noddy_new,                 /* tp_new */
};

static PyMethodDef module_methods[] = {
    {NULL}  /* Sentinel */
};

#ifndef PyMODINIT_FUNC	/* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif
PyMODINIT_FUNC
initnoddy2(void)
{
    PyObject* m;

    if (PyType_Ready(&NoddyType) < 0)
        return;

    m = Py_InitModule3("noddy2", module_methods,
                       "Example module that creates an extension type.");

    if (m == NULL)
        return;

    Py_INCREF(&NoddyType);
    PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);
}

此版本的模块有许多更改。

我们增加了一个额外的包括:

#include <structmember.h>

这包括提供用于处理属性的语句,如稍后所述。

Noddy对象结构的名称已缩写为Noddy。类型对象名称已缩短为NoddyType

现在,Noddy类型具有三个数据属性* first last number *。 * first last *变量是包含名字和姓氏的 Python 字符串。 * number *属性是一个整数。

对象结构将相应更新:

typedef struct {
    PyObject_HEAD
    PyObject *first;
    PyObject *last;
    int number;
} Noddy;

因为我们现在有数据要 Management,所以在对象分配和释放方面我们必须更加小心。至少,我们需要一个释放方法:

static void
Noddy_dealloc(Noddy* self)
{
    Py_XDECREF(self->first);
    Py_XDECREF(self->last);
    Py_TYPE(self)->tp_free((PyObject*)self);
}

分配给tp_dealloc成员:

(destructor)Noddy_dealloc, /*tp_dealloc*/

此方法减少两个 Python 属性的引用计数。我们在这里使用Py_XDECREF()是因为firstlast成员可以是* NULL *。然后,它将调用该对象类型的tp_free成员以释放该对象的内存。请注意,对象的类型可能不是NoddyType,因为该对象可能是子类的实例。

我们要确保名字和姓氏都初始化为空字符串,因此我们提供了一个新方法:

static PyObject *
Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    Noddy *self;

    self = (Noddy *)type->tp_alloc(type, 0);
    if (self != NULL) {
        self->first = PyString_FromString("");
        if (self->first == NULL)
          {
            Py_DECREF(self);
            return NULL;
          }

        self->last = PyString_FromString("");
        if (self->last == NULL)
          {
            Py_DECREF(self);
            return NULL;
          }

        self->number = 0;
    }

    return (PyObject *)self;
}

并将其安装在tp_new成员中:

Noddy_new,                 /* tp_new */

新成员负责创建(而不是初始化)该类型的对象。它在 Python 中以new()方法公开。有关new()方法的详细讨论,请参见标题为“在 Python 中统一类型和类”的论文。实施新方法的一个原因是要确保实例变量的初始值。在这种情况下,我们使用新方法来确保成员firstlast的初始值不是* NULL 。如果我们不在乎初始值是否为 NULL ,则可以像以前一样使用PyType_GenericNew()作为新方法。 PyType_GenericNew()将所有实例变量成员初始化为 NULL *。

新方法是一个静态方法,该方法将传递要实例化的类型,并在调用该类型时传递任何参数,并返回创建的新对象。新方法始终接受位置参数和关键字参数,但是它们通常会忽略参数,而将参数处理留给初始化方法。请注意,如果类型支持子类化,则传递的类型可能不是正在定义的类型。新方法调用 tp_alloc 插槽分配内存。我们不会自己填写tp_alloc插槽。相反,PyType_Ready()pass从我们的 Base Class(默认是object)继承来为我们填充它。大多数类型使用默认分配。

Note

如果要创建合作社tp_new(调用基本类型的tp_newnew()的合作社),则必须不要try在运行时使用方法解析 Sequences 来确定要调用的方法。始终静态确定要调用的类型,然后直接或passtype->tp_base->tp_new调用它的tp_new。如果不执行此操作,则您的类型的 Python 子类也将从其他 Python 定义的类继承而来,则可能无法正常工作。 (特别是,如果没有得到TypeError,则可能无法创建此类子类的实例。)

我们提供了一个初始化函数:

static int
Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
{
    PyObject *first=NULL, *last=NULL, *tmp;

    static char *kwlist[] = {"first", "last", "number", NULL};

    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;
}

pass填充tp_init插槽。

(initproc)Noddy_init,         /* tp_init */

tp_init插槽在 Python 中以init()方法公开。创建对象后,它用于初始化对象。与新方法不同,我们不能保证会调用初始化程序。取消对象拾取时不调用初始化程序,可以将其覆盖。我们的初始化程序接受参数来为我们的实例提供初始值。初始化程序始终接受位置和关键字参数。

可以多次调用初始化程序。任何人都可以在我们的对象上调用init()方法。因此,在分配新值时我们必须格外小心。例如,我们可能很想分配first成员:

if (first) {
    Py_XDECREF(self->first);
    Py_INCREF(first);
    self->first = first;
}

但这是有风险的。我们的类型不限制first成员的类型,因此它可以是任何类型的对象。它可能有一个析构函数,导致执行试图访问first成员的代码。为了保持偏执并保护自己免受这种可能性的困扰,我们几乎总是在减少其引用计数之前重新分配成员。我们什么时候不必这样做?

  • 当我们绝对知道参考计数大于 1

  • 当我们知道对象[1]的释放不会导致对我们类型代码的任何调用

  • 当不支持垃圾收集[2]时在tp_dealloc处理程序中减少引用计数时

我们想将实例变量作为属性公开。有很多方法可以做到这一点。最简单的方法是定义成员定义:

static PyMemberDef Noddy_members[] = {
    {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,
     "first name"},
    {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,
     "last name"},
    {"number", T_INT, offsetof(Noddy, number), 0,
     "noddy number"},
    {NULL}  /* Sentinel */
};

并将定义放在tp_members插槽中:

Noddy_members,             /* tp_members */

每个成员定义都有一个成员名称,类型,偏移量,访问标志和文档字符串。有关详细信息,请参见下面的通用属性 Management部分。

这种方法的缺点是它没有提供一种方法来限制可以分配给 Python 属性的对象的类型。我们希望名字和姓氏是字符串,但是可以分配任何 Python 对象。此外,可以删除属性,将 C 指针设置为* NULL 。即使我们可以确保将成员初始化为非 NULL 值,但是如果删除了属性,则可以将成员设置为 NULL *。

我们定义了一个单一方法name(),该方法将对象名称输出为名字和姓氏的串联。

static PyObject *
Noddy_name(Noddy* self)
{
    static PyObject *format = NULL;
    PyObject *args, *result;

    if (format == NULL) {
        format = PyString_FromString("%s %s");
        if (format == NULL)
            return NULL;
    }

    if (self->first == NULL) {
        PyErr_SetString(PyExc_AttributeError, "first");
        return NULL;
    }

    if (self->last == NULL) {
        PyErr_SetString(PyExc_AttributeError, "last");
        return NULL;
    }

    args = Py_BuildValue("OO", self->first, self->last);
    if (args == NULL)
        return NULL;

    result = PyString_Format(format, args);
    Py_DECREF(args);

    return result;
}

该方法实现为 C 函数,该函数将Noddy(或Noddy子类)实例作为第一个参数。方法始终将实例作为第一个参数。方法通常也接受位置和关键字参数,但是在这种情况下,我们不接受任何方法,也不需要接受位置参数 Tuples 或关键字参数字典。此方法等效于 Python 方法:

def name(self):
   return "%s %s" % (self.first, self.last)

请注意,我们必须检查firstlast成员为* NULL 的可能性。这是因为可以删除它们,在这种情况下,它们将被设置为 NULL *。最好防止删除这些属性并将属性值限制为字符串。我们将在下一节中看到如何做。

现在我们已经定义了方法,我们需要创建一个方法定义数组:

static PyMethodDef Noddy_methods[] = {
    {"name", (PyCFunction)Noddy_name, METH_NOARGS,
     "Return the name, combining the first and last name"
    },
    {NULL}  /* Sentinel */
};

并将它们分配给tp_methods插槽:

Noddy_methods,             /* tp_methods */

请注意,我们使用METH_NOARGS标志来指示该方法未传递任何参数。

最后,我们将类型用作 Base Class。到目前为止,我们已经仔细地编写了我们的方法,以使它们对创建或使用的对象的类型不做任何假设,因此我们要做的就是将Py_TPFLAGS_BASETYPE添加到类标志定义中:

Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/

我们将initnoddy()重命名为initnoddy2()并更新传递给Py_InitModule3()的模块名称。

最后,我们更新setup.py文件以构建新模块:

from distutils.core import setup, Extension
setup(name="noddy", version="1.0",
      ext_modules=[
         Extension("noddy", ["noddy.c"]),
         Extension("noddy2", ["noddy2.c"]),
         ])

2.1.2. 提供对数据属性的更好控制

在本节中,我们将更好地控制Noddy示例中firstlast属性的设置方式。在我们模块的先前版本中,实例变量firstlast可以设置为非字符串值,甚至可以删除。我们要确保这些属性始终包含字符串。

#include <Python.h>
#include "structmember.h"

typedef struct {
    PyObject_HEAD
    PyObject *first;
    PyObject *last;
    int number;
} Noddy;

static void
Noddy_dealloc(Noddy* self)
{
    Py_XDECREF(self->first);
    Py_XDECREF(self->last);
    Py_TYPE(self)->tp_free((PyObject*)self);
}

static PyObject *
Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    Noddy *self;

    self = (Noddy *)type->tp_alloc(type, 0);
    if (self != NULL) {
        self->first = PyString_FromString("");
        if (self->first == NULL) {
            Py_DECREF(self);
            return NULL;
        }

        self->last = PyString_FromString("");
        if (self->last == NULL) {
            Py_DECREF(self);
            return NULL;
        }

        self->number = 0;
    }

    return (PyObject *)self;
}

static int
Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
{
    PyObject *first=NULL, *last=NULL, *tmp;

    static char *kwlist[] = {"first", "last", "number", NULL};

    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|SSi", 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;
}

static PyMemberDef Noddy_members[] = {
    {"number", T_INT, offsetof(Noddy, number), 0,
     "noddy number"},
    {NULL}  /* Sentinel */
};

static PyObject *
Noddy_getfirst(Noddy *self, void *closure)
{
    Py_INCREF(self->first);
    return self->first;
}

static int
Noddy_setfirst(Noddy *self, PyObject *value, void *closure)
{
    if (value == NULL) {
        PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");
        return -1;
    }

    if (! PyString_Check(value)) {
        PyErr_SetString(PyExc_TypeError,
                        "The first attribute value must be a string");
        return -1;
    }

    Py_DECREF(self->first);
    Py_INCREF(value);
    self->first = value;

    return 0;
}

static PyObject *
Noddy_getlast(Noddy *self, void *closure)
{
    Py_INCREF(self->last);
    return self->last;
}

static int
Noddy_setlast(Noddy *self, PyObject *value, void *closure)
{
    if (value == NULL) {
        PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");
        return -1;
    }

    if (! PyString_Check(value)) {
        PyErr_SetString(PyExc_TypeError,
                        "The last attribute value must be a string");
        return -1;
    }

    Py_DECREF(self->last);
    Py_INCREF(value);
    self->last = value;

    return 0;
}

static PyGetSetDef Noddy_getseters[] = {
    {"first",
     (getter)Noddy_getfirst, (setter)Noddy_setfirst,
     "first name",
     NULL},
    {"last",
     (getter)Noddy_getlast, (setter)Noddy_setlast,
     "last name",
     NULL},
    {NULL}  /* Sentinel */
};

static PyObject *
Noddy_name(Noddy* self)
{
    static PyObject *format = NULL;
    PyObject *args, *result;

    if (format == NULL) {
        format = PyString_FromString("%s %s");
        if (format == NULL)
            return NULL;
    }

    args = Py_BuildValue("OO", self->first, self->last);
    if (args == NULL)
        return NULL;

    result = PyString_Format(format, args);
    Py_DECREF(args);

    return result;
}

static PyMethodDef Noddy_methods[] = {
    {"name", (PyCFunction)Noddy_name, METH_NOARGS,
     "Return the name, combining the first and last name"
    },
    {NULL}  /* Sentinel */
};

static PyTypeObject NoddyType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "noddy.Noddy",             /* tp_name */
    sizeof(Noddy),             /* tp_basicsize */
    0,                         /* tp_itemsize */
    (destructor)Noddy_dealloc, /* tp_dealloc */
    0,                         /* tp_print */
    0,                         /* tp_getattr */
    0,                         /* tp_setattr */
    0,                         /* tp_compare */
    0,                         /* 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 */
    Py_TPFLAGS_DEFAULT |
        Py_TPFLAGS_BASETYPE,   /* tp_flags */
    "Noddy objects",           /* tp_doc */
    0,                         /* tp_traverse */
    0,                         /* tp_clear */
    0,                         /* tp_richcompare */
    0,                         /* tp_weaklistoffset */
    0,                         /* tp_iter */
    0,                         /* tp_iternext */
    Noddy_methods,             /* tp_methods */
    Noddy_members,             /* tp_members */
    Noddy_getseters,           /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    (initproc)Noddy_init,      /* tp_init */
    0,                         /* tp_alloc */
    Noddy_new,                 /* tp_new */
};

static PyMethodDef module_methods[] = {
    {NULL}  /* Sentinel */
};

#ifndef PyMODINIT_FUNC	/* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif
PyMODINIT_FUNC
initnoddy3(void)
{
    PyObject* m;

    if (PyType_Ready(&NoddyType) < 0)
        return;

    m = Py_InitModule3("noddy3", module_methods,
                       "Example module that creates an extension type.");

    if (m == NULL)
        return;

    Py_INCREF(&NoddyType);
    PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);
}

为了提供对firstlast属性的更好控制,我们将使用自定义 getter 和 setter 函数。以下是获取和设置first属性的Function:

Noddy_getfirst(Noddy *self, void *closure)
{
    Py_INCREF(self->first);
    return self->first;
}

static int
Noddy_setfirst(Noddy *self, PyObject *value, void *closure)
{
  if (value == NULL) {
    PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");
    return -1;
  }

  if (! PyString_Check(value)) {
    PyErr_SetString(PyExc_TypeError,
                    "The first attribute value must be a string");
    return -1;
  }

  Py_DECREF(self->first);
  Py_INCREF(value);
  self->first = value;

  return 0;
}

getter 函数将传递一个Noddy对象和一个“闭包”,这是空指针。在这种情况下,关闭将被忽略。 (闭包支持将定义数据传递到 getter 和 setter 的高级用法.例如,这可用于允许使用一组 getter 和 setter 函数,这些函数根据该属性中的数据来决定要获取或设置的属性.关闭.)

setter 函数将传递Noddy对象,新值和闭包。新值可能是* NULL *,在这种情况下,该属性将被删除。在我们的 setter 中,如果属性被删除或属性值不是字符串,则会引发错误。

我们创建一个PyGetSetDef结构的数组:

static PyGetSetDef Noddy_getseters[] = {
    {"first",
     (getter)Noddy_getfirst, (setter)Noddy_setfirst,
     "first name",
     NULL},
    {"last",
     (getter)Noddy_getlast, (setter)Noddy_setlast,
     "last name",
     NULL},
    {NULL}  /* Sentinel */
};

并将其注册在tp_getset插槽中:

Noddy_getseters,           /* tp_getset */

注册我们的属性获取器和设置器。

PyGetSetDef结构中的最后一项是上面提到的闭包。在这种情况下,我们不使用闭包,因此我们只传递* NULL *。

我们还将删除这些属性的成员定义:

static PyMemberDef Noddy_members[] = {
    {"number", T_INT, offsetof(Noddy, number), 0,
     "noddy number"},
    {NULL}  /* Sentinel */
};

我们还需要更新tp_init处理程序,以仅允许传递字符串[3]

static int
Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
{
    PyObject *first=NULL, *last=NULL, *tmp;

    static char *kwlist[] = {"first", "last", "number", NULL};

    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|SSi", 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;
}

pass这些更改,我们可以确保firstlast成员永远不会是* NULL ,因此我们几乎可以在所有情况下都删除对 NULL *值的检查。这意味着大多数Py_XDECREF()通话可以转换为Py_DECREF()通话。我们唯一不能更改这些调用的地方是在 deallocator 中,在这些地方,这些成员的初始化有可能在构造函数中失败。

与之前一样,我们还重命名了模块初始化函数和初始化函数中的模块名称,并在setup.py文件中添加了额外的定义。

2.1.3. 支持循环垃圾收集

Python 有一个循环垃圾收集器,即使它们的引用计数不为零,它也可以识别不需要的对象。当对象参与循环时,可能会发生这种情况。例如,考虑:

>>> l = []
>>> l.append(l)
>>> del l

在此示例中,我们创建一个包含自身的列表。当我们删除它时,它本身仍然具有引用。它的参考计数不会降为零。幸运的是,Python 的循环垃圾收集器finally将发现该列表是垃圾并释放了它。

Noddy示例的第二个版本中,我们允许将任何类型的对象存储在firstlast属性[4]中。这意味着Noddy个对象可以参与循环:

>>> import noddy2
>>> n = noddy2.Noddy()
>>> l = [n]
>>> n.first = l

这很愚蠢,但是它为我们提供了一个借口,以在Noddy示例中添加对循环垃圾收集器的支持。为了支持循环垃圾收集,类型需要填充两个插槽并设置一个启用这些插槽的类标志:

#include <Python.h>
#include "structmember.h"

typedef struct {
    PyObject_HEAD
    PyObject *first;
    PyObject *last;
    int number;
} Noddy;

static int
Noddy_traverse(Noddy *self, visitproc visit, void *arg)
{
    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;
}

static int
Noddy_clear(Noddy *self)
{
    PyObject *tmp;

    tmp = self->first;
    self->first = NULL;
    Py_XDECREF(tmp);

    tmp = self->last;
    self->last = NULL;
    Py_XDECREF(tmp);

    return 0;
}

static void
Noddy_dealloc(Noddy* self)
{
    PyObject_GC_UnTrack(self);
    Noddy_clear(self);
    Py_TYPE(self)->tp_free((PyObject*)self);
}

static PyObject *
Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    Noddy *self;

    self = (Noddy *)type->tp_alloc(type, 0);
    if (self != NULL) {
        self->first = PyString_FromString("");
        if (self->first == NULL) {
            Py_DECREF(self);
            return NULL;
        }

        self->last = PyString_FromString("");
        if (self->last == NULL) {
            Py_DECREF(self);
            return NULL;
        }

        self->number = 0;
    }

    return (PyObject *)self;
}

static int
Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
{
    PyObject *first=NULL, *last=NULL, *tmp;

    static char *kwlist[] = {"first", "last", "number", NULL};

    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;
}

static PyMemberDef Noddy_members[] = {
    {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,
     "first name"},
    {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,
     "last name"},
    {"number", T_INT, offsetof(Noddy, number), 0,
     "noddy number"},
    {NULL}  /* Sentinel */
};

static PyObject *
Noddy_name(Noddy* self)
{
    static PyObject *format = NULL;
    PyObject *args, *result;

    if (format == NULL) {
        format = PyString_FromString("%s %s");
        if (format == NULL)
            return NULL;
    }

    if (self->first == NULL) {
        PyErr_SetString(PyExc_AttributeError, "first");
        return NULL;
    }

    if (self->last == NULL) {
        PyErr_SetString(PyExc_AttributeError, "last");
        return NULL;
    }

    args = Py_BuildValue("OO", self->first, self->last);
    if (args == NULL)
        return NULL;

    result = PyString_Format(format, args);
    Py_DECREF(args);

    return result;
}

static PyMethodDef Noddy_methods[] = {
    {"name", (PyCFunction)Noddy_name, METH_NOARGS,
     "Return the name, combining the first and last name"
    },
    {NULL}  /* Sentinel */
};

static PyTypeObject NoddyType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "noddy.Noddy",             /* tp_name */
    sizeof(Noddy),             /* tp_basicsize */
    0,                         /* tp_itemsize */
    (destructor)Noddy_dealloc, /* tp_dealloc */
    0,                         /* tp_print */
    0,                         /* tp_getattr */
    0,                         /* tp_setattr */
    0,                         /* tp_compare */
    0,                         /* 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 */
    Py_TPFLAGS_DEFAULT |
        Py_TPFLAGS_BASETYPE |
        Py_TPFLAGS_HAVE_GC,    /* tp_flags */
    "Noddy objects",           /* tp_doc */
    (traverseproc)Noddy_traverse,   /* tp_traverse */
    (inquiry)Noddy_clear,           /* tp_clear */
    0,                         /* tp_richcompare */
    0,                         /* tp_weaklistoffset */
    0,                         /* tp_iter */
    0,                         /* tp_iternext */
    Noddy_methods,             /* tp_methods */
    Noddy_members,             /* tp_members */
    0,                         /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    (initproc)Noddy_init,      /* tp_init */
    0,                         /* tp_alloc */
    Noddy_new,                 /* tp_new */
};

static PyMethodDef module_methods[] = {
    {NULL}  /* Sentinel */
};

#ifndef PyMODINIT_FUNC	/* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif
PyMODINIT_FUNC
initnoddy4(void)
{
    PyObject* m;

    if (PyType_Ready(&NoddyType) < 0)
        return;

    m = Py_InitModule3("noddy4", module_methods,
                       "Example module that creates an extension type.");

    if (m == NULL)
        return;

    Py_INCREF(&NoddyType);
    PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);
}

遍历方法提供对可能参与循环的子对象的访问:

static int
Noddy_traverse(Noddy *self, visitproc visit, void *arg)
{
    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()函数,该函数将传递给遍历方法。 visit()函数将子对象和传递给遍历方法的额外参数* arg *作为参数。它返回一个整数值,如果该值非零,则必须返回该整数。

Python 2.4 及更高版本提供了Py_VISIT()宏,该宏可以自动调用访问函数。使用Py_VISIT(),可以简化Noddy_traverse()

static int
Noddy_traverse(Noddy *self, visitproc visit, void *arg)
{
    Py_VISIT(self->first);
    Py_VISIT(self->last);
    return 0;
}

Note

请注意,tp_traverse实现必须使用* visit arg *精确命名其参数,才能使用Py_VISIT()。这是为了鼓励这些无聊的实现之间的一致性。

我们还需要提供一种清除可能参与循环的子对象的方法。

static int
Noddy_clear(Noddy *self)
{
    PyObject *tmp;

    tmp = self->first;
    self->first = NULL;
    Py_XDECREF(tmp);

    tmp = self->last;
    self->last = NULL;
    Py_XDECREF(tmp);

    return 0;
}

请注意,在Noddy_clear()中使用了一个临时变量。我们使用临时变量,以便我们可以在减少其引用计数之前将每个成员设置为* NULL 。我们这样做是因为,如前所述,如果引用计数降至零,则可能导致代码运行并返回对象。另外,因为我们现在支持垃圾回收,所以我们还必须担心正在运行的触发垃圾回收的代码。如果运行垃圾回收,则可以调用我们的tp_traverse处理程序。当成员的引用计数下降到零且其值尚未设置为 NULL *时,我们就无法冒用Noddy_traverse()的机会。

Python 2.4 及更高版本提供了一个Py_CLEAR(),以自动谨慎地减少引用计数。使用Py_CLEAR(),可以简化Noddy_clear()函数:

static int
Noddy_clear(Noddy *self)
{
    Py_CLEAR(self->first);
    Py_CLEAR(self->last);
    return 0;
}

请注意,Noddy_dealloc()可以pass__del__方法或 weakref 回调调用任意函数。这意味着可以在函数内部触发循环 GC。由于 GC 假定引用计数不为零,因此我们需要在清除成员之前pass调用PyObject_GC_UnTrack()来从 GC 取消跟踪对象。这是使用PyObject_GC_UnTrack()Noddy_clear()的重新实现的分配器。

static void
Noddy_dealloc(Noddy* self)
{
    PyObject_GC_UnTrack(self);
    Noddy_clear(self);
    Py_TYPE(self)->tp_free((PyObject*)self);
}

最后,我们将Py_TPFLAGS_HAVE_GC标志添加到类标志中:

Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /* tp_flags */

就是这样。如果我们编写了自定义的tp_alloctp_free插槽,则需要对其进行修改以进行循环垃圾收集。大多数扩展将使用自动提供的版本。

2.1.4. 子类化其他类型

可以创建从现有类型派生的新扩展类型。从扩展类型中继承最容易,因为扩展可以轻松使用所需的PyTypeObject。在扩展模块之间共享这些PyTypeObject结构可能很困难。

在此示例中,我们将创建一个从内置list类型继承的Shoddy类型。新类型将与常规列表完全兼容,但是将具有附加的increment()方法,该方法会增加内部计数器。

>>> import shoddy
>>> s = shoddy.Shoddy(range(3))
>>> s.extend(s)
>>> print len(s)
6
>>> print s.increment()
1
>>> print s.increment()
2
#include <Python.h>

typedef struct {
    PyListObject list;
    int state;
} Shoddy;

static PyObject *
Shoddy_increment(Shoddy *self, PyObject *unused)
{
    self->state++;
    return PyInt_FromLong(self->state);
}

static PyMethodDef Shoddy_methods[] = {
    {"increment", (PyCFunction)Shoddy_increment, METH_NOARGS,
     PyDoc_STR("increment state counter")},
    {NULL,	NULL},
};

static int
Shoddy_init(Shoddy *self, PyObject *args, PyObject *kwds)
{
    if (PyList_Type.tp_init((PyObject *)self, args, kwds) < 0)
        return -1;
    self->state = 0;
    return 0;
}

static PyTypeObject ShoddyType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "shoddy.Shoddy",         /* tp_name */
    sizeof(Shoddy),          /* tp_basicsize */
    0,                       /* tp_itemsize */
    0,                       /* tp_dealloc */
    0,                       /* tp_print */
    0,                       /* tp_getattr */
    0,                       /* tp_setattr */
    0,                       /* tp_compare */
    0,                       /* 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 */
    Py_TPFLAGS_DEFAULT |
        Py_TPFLAGS_BASETYPE, /* tp_flags */
    0,                       /* tp_doc */
    0,                       /* tp_traverse */
    0,                       /* tp_clear */
    0,                       /* tp_richcompare */
    0,                       /* tp_weaklistoffset */
    0,                       /* tp_iter */
    0,                       /* tp_iternext */
    Shoddy_methods,          /* 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 */
    (initproc)Shoddy_init,   /* tp_init */
    0,                       /* tp_alloc */
    0,                       /* tp_new */
};

PyMODINIT_FUNC
initshoddy(void)
{
    PyObject *m;

    ShoddyType.tp_base = &PyList_Type;
    if (PyType_Ready(&ShoddyType) < 0)
        return;

    m = Py_InitModule3("shoddy", NULL, "Shoddy module");
    if (m == NULL)
        return;

    Py_INCREF(&ShoddyType);
    PyModule_AddObject(m, "Shoddy", (PyObject *) &ShoddyType);
}

如您所见,源代码与上一节中的Noddy示例非常相似。我们将分解它们之间的主要区别。

typedef struct {
    PyListObject list;
    int state;
} Shoddy;

派生类型对象的主要区别在于,基本类型的对象结构必须是第一个值。基本类型将在其结构的开头已经包含PyObject_HEAD()

当 Python 对象是Shoddy实例时,其* PyObject 指针可以安全地强制转换为 PyListObject* 和* Shoddy **。

static int
Shoddy_init(Shoddy *self, PyObject *args, PyObject *kwds)
{
    if (PyList_Type.tp_init((PyObject *)self, args, kwds) < 0)
       return -1;
    self->state = 0;
    return 0;
}

在我们类型的__init__方法中,我们可以看到如何调用基本类型的__init__方法。

使用自定义newdealloc方法编写类型时,此模式很重要。 new方法实际上不应为带有tp_alloc的对象创建内存,该内存将由 Base Class 在调用其tp_new时处理。

在为Shoddy类型填写PyTypeObject()时,您会看到tp_base()的插槽。由于跨平台编译器的问题,您不能直接使用PyList_Type()填充该字段;稍后可以在模块的init()函数中完成。

PyMODINIT_FUNC
initshoddy(void)
{
    PyObject *m;

    ShoddyType.tp_base = &PyList_Type;
    if (PyType_Ready(&ShoddyType) < 0)
        return;

    m = Py_InitModule3("shoddy", NULL, "Shoddy module");
    if (m == NULL)
        return;

    Py_INCREF(&ShoddyType);
    PyModule_AddObject(m, "Shoddy", (PyObject *) &ShoddyType);
}

在调用PyType_Ready()之前,类型结构必须填充tp_base插槽。在派生新类型时,不必用PyType_GenericNew()填充tp_alloc插槽-基本类型的分配Function将被继承。

之后,调用PyType_Ready()并将类型对象添加到模块中与基本Noddy示例相同。

2.2. 类型方法

本节旨在快速介绍您可以实现的各种类型的方法及其作用。

这是PyTypeObject的定义,其中一些仅在调试版本中使用的字段被Ellipsis:

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

    /* Methods to implement standard operations */

    destructor tp_dealloc;
    printfunc tp_print;
    getattrfunc tp_getattr;
    setattrfunc tp_setattr;
    cmpfunc tp_compare;
    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 */
    long tp_flags;

    char *tp_doc; /* Documentation string */

    /* Assigned meaning in release 2.0 */
    /* call function for all accessible objects */
    traverseproc tp_traverse;

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

    /* Assigned meaning in release 2.1 */
    /* rich comparisons */
    richcmpfunc tp_richcompare;

    /* weak reference enabler */
    long tp_weaklistoffset;

    /* Added in release 2.2 */
    /* 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;
    long 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;

} PyTypeObject;

现在,这是很多方法。不过,不必太担心-如果您要定义一个类型,则很有可能只实现其中的少数几种。

正如您现在可能期望的那样,我们将介绍这一点,并提供有关各种处理程序的更多信息。我们不会按照结构中定义的 Sequences 进行排序,因为有很多历史包 bag 会影响字段的排序;确保您的类型初始化使字段 Sequences 正确!通常,最容易找到一个包含所有所需字段的示例(即使它们已初始化为0),然后更改值以适合您的新类型。

char *tp_name; /* For printing */

类型的名称-如上一节所述,它会出现在不同的地方,几乎完全出于诊断目的。try选择在这种情况下会有所帮助的东西!

int tp_basicsize, tp_itemsize; /* For allocation */

这些字段告诉运行时创建此类型的新对象时要分配多少内存。 Python 内置了对可变长度结构(例如:字符串,列表)的支持,这是tp_itemsize字段所在的位置。稍后将对此进行处理。

char *tp_doc;

在这里,您可以放置一个当 Python 脚本引用obj.__doc__时要返回的字符串(或其地址)以检索 doc 字符串。

现在我们来介绍基本的类型方法-大多数扩展类型将实现的方法。

2.2.1. 完成和取消分配

destructor tp_dealloc;

当您的类型实例的引用计数减少到零并且 Python 解释器想要回收该函数时,将调用此函数。如果您的类型有可用的内存或要执行的其他清理操作,则可以将其放在此处。对象本身也需要在这里释放。这是此Function的示例:

static void
newdatatype_dealloc(newdatatypeobject * obj)
{
    free(obj->obj_UnderlyingDatatypePtr);
    Py_TYPE(obj)->tp_free(obj);
}

解除分配器Function的一项重要要求是,它应独自保留所有未决异常。这很重要,因为在解释器展开 Python 堆栈时,经常会调用 deallocator。当由于异常(而不是正常返回)而取消堆栈堆栈时,不会采取任何措施来保护解除分配程序,以防止看到已设置异常。解除分配器执行的任何可能导致执行附加 Python 代码的操作都可以检测到已设置了异常。这可能会导致解释器产生误导性错误。防止这种情况发生的正确方法是在执行不安全操作之前保存未决异常,并在完成后将其还原。可以使用PyErr_Fetch()PyErr_Restore()函数完成此操作:

static void
my_dealloc(PyObject *obj)
{
    MyObject *self = (MyObject *) obj;
    PyObject *cbresult;

    if (self->my_callback != NULL) {
        PyObject *err_type, *err_value, *err_traceback;
        int have_error = PyErr_Occurred() ? 1 : 0;

        if (have_error)
            PyErr_Fetch(&err_type, &err_value, &err_traceback);

        cbresult = PyObject_CallObject(self->my_callback, NULL);
        if (cbresult == NULL)
            PyErr_WriteUnraisable(self->my_callback);
        else
            Py_DECREF(cbresult);

        if (have_error)
            PyErr_Restore(err_type, err_value, err_traceback);

        Py_DECREF(self->my_callback);
    }
    Py_TYPE(obj)->tp_free((PyObject*)self);
}

2.2.2. 对象呈现

在 Python 中,有三种方法可以生成对象的文本表示形式:repr()函数(或等效的反引号语法),str()函数和print语句。对于大多数对象,print语句等效于str()函数,但是如有必要,可以特殊情况下打印到FILE*。仅当确定效率是一个问题并且分析表明创建要写入文件的临时字符串对象时,才应该这样做。

这些处理程序都是可选的,大多数类型最多最多需要实现tp_strtp_repr处理程序。

reprfunc tp_repr;
reprfunc tp_str;
printfunc tp_print;

tp_repr处理程序应返回一个字符串对象,其中包含对其调用的实例的表示。这是一个简单的示例:

static PyObject *
newdatatype_repr(newdatatypeobject * obj)
{
    return PyString_FromFormat("Repr-ified_newdatatype{{size:\%d}}",
                               obj->obj_UnderlyingDatatypePtr->size);
}

如果未指定tp_repr处理程序,则解释器将提供使用该类型的tp_name的表示形式以及该对象的唯一标识值。

tp_str处理程序用于str(),上述tp_repr处理程序针对repr();也就是说,当 Python 代码在您的对象实例上调用str()时,将调用该方法。它的实现与tp_repr函数非常相似,但是生成的字符串供人类使用。如果未指定tp_str,则使用tp_repr处理程序。

这是一个简单的示例:

static PyObject *
newdatatype_str(newdatatypeobject * obj)
{
    return PyString_FromFormat("Stringified_newdatatype{{size:\%d}}",
                               obj->obj_UnderlyingDatatypePtr->size);
}

每当 Python 需要“打印”该类型的实例时,都会调用 print 函数。例如,如果“节点”是 TreeNode 类型的实例,则在 Python 代码调用时将调用 print 函数:

print node

有一个 flags 参数和一个标志Py_PRINT_RAW,它建议您在打印时不带引号,也可能不解释转义序列。

打印Function接收文件对象作为参数。您可能要写入该文件对象。

这是一个示例打印Function:

static int
newdatatype_print(newdatatypeobject *obj, FILE *fp, int flags)
{
    if (flags & Py_PRINT_RAW) {
        fprintf(fp, "<{newdatatype object--size: %d}>",
                obj->obj_UnderlyingDatatypePtr->size);
    }
    else {
        fprintf(fp, "\"<{newdatatype object--size: %d}>\"",
                obj->obj_UnderlyingDatatypePtr->size);
    }
    return 0;
}

2.2.3. 属性 Management

对于每个可以支持属性的对象,相应的类型必须提供控制如何解析属性的Function。需要有一个函数可以检索属性(如果已定义),而另一个函数可以设置属性(如果允许设置属性)。删除属性是一种特殊情况,对于这种情况,传递给处理程序的新值是* NULL *。

Python 支持两对属性处理程序。支持属性的类型只需要实现PairFunction即可。区别在于,Pair将属性名称命名为char*,而另Pair则接受PyObject*。每种类型都可以使用对实现更方便的任何对。

getattrfunc  tp_getattr;        /* char * version */
setattrfunc  tp_setattr;
/* ... */
getattrofunc tp_getattrofunc;   /* PyObject * version */
setattrofunc tp_setattrofunc;

如果访问对象的属性始终是一个简单的操作(稍后将对此进行说明),则可以使用一些通用实现来提供属性 Management Function的PyObject*版本。从 Python 2.2 开始,对特定于类型的属性处理程序的实际需求几乎完全消失了,尽管有许多示例尚未更新以使用某些可用的新通用机制。

2.2.3.1. 通用属性 Management

2.2 版中的新Function。

大多数扩展类型仅使用* simple *属性。那么,什么使属性简单?仅需满足几个条件:

  • 调用PyType_Ready()时必须知道属性的名称。

  • 无需进行特殊处理即可记录已查找或设置的属性,也无需根据该值采取任何操作。

请注意,此列表对属性的值,计算值的时间或相关数据的存储方式没有任何限制。

调用PyType_Ready()时,它将使用类型对象引用的三个表来创建descriptor,并将它们放置在类型对象的字典中。每个 Descriptors 控制对实例对象的一个属性的访问。每个表都是可选的;如果所有三个均为* NULL ,则该类型的实例将仅具有从其基本类型继承的属性,并且还应保留tp_getattrotp_setattro字段 NULL *,以允许基本类型处理属性。

这些表被语句为类型对象的三个字段:

struct PyMethodDef *tp_methods;
struct PyMemberDef *tp_members;
struct PyGetSetDef *tp_getset;

如果tp_methods不是* NULL *,则它必须引用PyMethodDef结构的数组。表中的每个条目都是此结构的一个实例:

typedef struct PyMethodDef {
    const char  *ml_name;       /* method name */
    PyCFunction  ml_meth;       /* implementation function */
    int          ml_flags;      /* flags */
    const char  *ml_doc;        /* docstring */
} PyMethodDef;

应该为该类型提供的每种方法定义一个条目。从基本类型继承的方法不需要任何条目。最后需要一个额外的条目。它是标记数组结尾的标记。前哨的ml_name字段必须为* NULL *。

XXX 需要参考结构字段的一些统一讨论,与下一部分共享。

第二个表用于定义直接 Map 到实例中存储的数据的属性。支持多种原始 C 类型,访问可以是只读或读写的。表中的结构定义为:

typedef struct PyMemberDef {
    char *name;
    int   type;
    int   offset;
    int   flags;
    char *doc;
} PyMemberDef;

对于表中的每个条目,将构造一个descriptor并将其添加到类型中,该类型将能够从实例结构中提取值。 type字段应包含structmember.hHeaders 中定义的类型代码之一;该值将用于确定如何将 Python 值与 C 值相互转换。 flags字段用于存储标志,这些标志控制如何访问属性。

XXX 需要将其中一些移至共享部分!

以下标志常量在structmember.h中定义;它们可以使用按位或运算进行组合。

ConstantMeaning
READONLYNever writable.
ROREADONLY的简写。
READ_RESTRICTED在受限模式下不可读。
WRITE_RESTRICTED在受限模式下不可写。
RESTRICTED在受限模式下不可读或不可写。

使用tp_members表构建在运行时使用的 Descriptors 的一个有趣的优点是,只需pass在表中提供文本,以此方式定义的任何属性都可以具有关联的文档字符串。应用程序可以使用自省 API 从类对象检索 Descriptors,并使用其__doc__属性获取 doc 字符串。

tp_methods表一样,需要name值为* NULL *的标记条目。

2.2.3.2. 特定类型的属性 Management

为简单起见,此处仅展示char*版本;名称参数的类型是界面的char*PyObject*风格之间的唯一区别。该示例实际上执行与上述通用示例相同的操作,但是未使用 Python 2.2 中添加的通用支持。显示此内容的价值是双重的:它演示了如何以可移植到较旧版本的 Python 的方式完成基本属性 Management,并说明如何调用处理程序函数,以便在需要扩展其Function时,您将了解需要做什么。

当对象需要属性查询时,将调用tp_getattr处理程序。在将调用类的getattr()方法的相同情况下将调用它。

解决此问题的一种可能方法是(1)实现一组函数(例如下面的示例中的newdatatype_getSize()newdatatype_setSize()),(2)提供列出这些函数的方法表,以及(3)提供一个返回以下内容的 getattr 函数:在该表中查找的结果。方法表使用与类型对象的tp_methods字段相同的结构。

这是一个例子:

static PyMethodDef newdatatype_methods[] = {
    {"getSize", (PyCFunction)newdatatype_getSize, METH_VARARGS,
     "Return the current size."},
    {"setSize", (PyCFunction)newdatatype_setSize, METH_VARARGS,
     "Set the size."},
    {NULL, NULL, 0, NULL}           /* sentinel */
};

static PyObject *
newdatatype_getattr(newdatatypeobject *obj, char *name)
{
    return Py_FindMethod(newdatatype_methods, (PyObject *)obj, name);
}

当将调用类实例的setattr()delattr()方法时,将调用tp_setattr处理程序。当应删除属性时,第三个参数将为* NULL 。这是一个简单地引发异常的示例;如果这确实是您想要的,则tp_setattr处理程序应设置为 NULL *。

static int
newdatatype_setattr(newdatatypeobject *obj, char *name, PyObject *v)
{
    (void)PyErr_Format(PyExc_RuntimeError, "Read-only attribute: \%s", name);
    return -1;
}

2.2.4. 对象比较

cmpfunc tp_compare;

当需要比较且对象未实现与请求的比较匹配的特定丰富比较方法时,将调用tp_compare处理程序。 (如果已定义并且使用PyObject_Compare()PyObject_Cmp()函数,或者从 Python 使用cmp(),则始终使用它。)它类似于cmp()方法。如果* obj1 小于 obj2 ,则此函数应返回-1,如果相等则返回0,如果 obj1 大于 obj2 *则返回1。 (以前允许返回小于和大于的任意负整数或正整数;从 Python 2.2 开始,不再允许这样做.将来,其他返回值可能被赋予不同的含义.)

tp_compare处理程序可能会引发异常。在这种情况下,它应该返回负值。调用者必须使用PyErr_Occurred()测试异常。

这是一个示例实现:

static int
newdatatype_compare(newdatatypeobject * obj1, newdatatypeobject * obj2)
{
    long result;

    if (obj1->obj_UnderlyingDatatypePtr->size <
        obj2->obj_UnderlyingDatatypePtr->size) {
        result = -1;
    }
    else if (obj1->obj_UnderlyingDatatypePtr->size >
             obj2->obj_UnderlyingDatatypePtr->size) {
        result = 1;
    }
    else {
        result = 0;
    }
    return result;
}

2.2.5. 抽象协议支持

Python 支持多种抽象协议; 抽象对象层中记录了提供使用这些接口的特定接口。

在 Python 实现的开发早期就定义了许多这样的抽象接口。特别是,自开始以来,数字,Map 和序列协议一直是 Python 的一部分。随着时间的推移,已添加了其他协议。对于依赖于类型实现中多个处理程序例程的协议,较早的协议已定义为类型对象引用的处理程序的可选块。对于较新的协议,在主类型对象中还有其他插槽,并设置了一个标志位以指示该插槽存在并且应由解释程序检查。 (标志位并不表示插槽值不是* NULL *.可以将标志设置为指示插槽的存在,但是可能仍未填充插槽.)

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

如果希望对象能够像数字,序列或 Map 对象一样工作,则可以放置分别实现 C 类型PyNumberMethodsPySequenceMethodsPyMappingMethods的结构的地址。您可以自行填写适当的值。您可以在 Python 源代码发行版的Objects目录中找到使用这些示例的示例。

hashfunc tp_hash;

如果选择提供此函数,则该函数应为您的数据类型的实例返回哈希码。这是一个毫无意义的例子:

static long
newdatatype_hash(newdatatypeobject *obj)
{
    long result;
    result = obj->obj_UnderlyingDatatypePtr->size;
    result = result * 3;
    return result;
}
ternaryfunc tp_call;

例如,如果obj1是您的数据类型的实例,并且 Python 脚本包含obj1('hello'),则调用tp_call处理程序时,将在“调用”您的数据类型的实例时调用此函数。

该函数采用三个参数:

    • arg1 是作为调用主题的数据类型的实例。如果呼叫为obj1('hello'),则 arg1 *为obj1
    • arg2 *是一个包含调用参数的 Tuples。您可以使用PyArg_ParseTuple()提取参数。
    • arg3 是所传递的关键字参数的字典。如果这不是 NULL ,并且支持关键字参数,请使用PyArg_ParseTupleAndKeywords()提取参数。如果您不想支持关键字参数,而这不是 NULL *,请在TypeError上加一条消息,说明不支持关键字参数。

这是调用函数的实现的一个令人沮丧的示例。

/* Implement the call function.
 *    obj1 is the instance receiving the call.
 *    obj2 is a tuple containing the arguments to the call, in this
 *         case 3 strings.
 */
static PyObject *
newdatatype_call(newdatatypeobject *obj, PyObject *args, PyObject *other)
{
    PyObject *result;
    char *arg1;
    char *arg2;
    char *arg3;

    if (!PyArg_ParseTuple(args, "sss:call", &arg1, &arg2, &arg3)) {
        return NULL;
    }
    result = PyString_FromFormat(
        "Returning -- value: [\%d] arg1: [\%s] arg2: [\%s] arg3: [\%s]\n",
        obj->obj_UnderlyingDatatypePtr->size,
        arg1, arg2, arg3);
    printf("\%s", PyString_AS_STRING(result));
    return result;
}

XXX 需要在此处添加一些字段…

/* Added in release 2.2 */
/* Iterators */
getiterfunc tp_iter;
iternextfunc tp_iternext;

这些Function提供对迭代器协议的支持。任何希望在其内容上支持迭代的对象(可能在迭代过程中生成)都必须实现tp_iter处理程序。 tp_iter处理程序返回的对象必须同时实现tp_itertp_iternext处理程序。这两个处理程序仅使用一个参数(为其调用实例),并返回一个新引用。如果发生错误,他们应该设置一个异常并返回* NULL *。

对于表示可迭代集合的对象,tp_iter处理程序必须返回一个迭代器对象。迭代器对象负责维护迭代状态。对于可以支持多个彼此不干扰的迭代器的集合(如列表和 Tuples 所做的那样),应创建并返回一个新的迭代器。只能迭代一次的对象(通常由于迭代的副作用)应pass返回对自身的新引用来实现此处理程序,并且还应实现tp_iternext处理程序。文件对象就是此类迭代器的一个示例。

迭代器对象应实现两个处理程序。 tp_iter处理程序应返回对迭代器的新引用(这与只能以破坏性方式进行迭代的对象的tp_iter处理程序相同)。 tp_iternext处理程序应该在迭代中返回对下一个对象的新引用。如果迭代已经结束,它可能会返回* NULL 而不会设置异常,也可能会设置StopIteration;避免异常可以产生更好的性能。如果发生实际错误,则应设置一个异常并返回 NULL *。

2.2.6. 参考支持薄弱

Python 弱引用实现的目标之一是允许任何类型参与弱引用机制,而不会给那些无法从弱引用(例如数字)中受益的对象带来开销。

为了使对象弱引用,扩展必须在实例结构中包括一个PyObject*字段,以使用弱引用机制。它必须由对象的构造函数初始化为* NULL *。它还必须将相应类型对象的tp_weaklistoffset字段设置为该字段的偏移量。例如,实例类型使用以下结构定义:

typedef struct {
    PyObject_HEAD
    PyClassObject *in_class;       /* The class object */
    PyObject      *in_dict;        /* A dictionary */
    PyObject      *in_weakreflist; /* List of weak references */
} PyInstanceObject;

实例的静态语句类型对象是pass以下方式定义的:

PyTypeObject PyInstance_Type = {
    PyObject_HEAD_INIT(&PyType_Type)
    0,
    "module.instance",

    /* Lots of stuff omitted for brevity... */

    Py_TPFLAGS_DEFAULT,                         /* tp_flags */
    0,                                          /* tp_doc */
    0,                                          /* tp_traverse */
    0,                                          /* tp_clear */
    0,                                          /* tp_richcompare */
    offsetof(PyInstanceObject, in_weakreflist), /* tp_weaklistoffset */
};

类型构造函数负责将弱引用列表初始化为* NULL *:

static PyObject *
instance_new() {
    /* Other initialization stuff omitted for brevity */

    self->in_weakreflist = NULL;

    return (PyObject *) self;
}

唯一的补充是,析构函数需要调用弱引用 Management 器以清除所有弱引用。仅当弱引用列表为非* NULL *时才需要:

static void
instance_dealloc(PyInstanceObject *inst)
{
    /* Allocate temporaries if needed, but do not begin
       destruction just yet.
     */

    if (inst->in_weakreflist != NULL)
        PyObject_ClearWeakRefs((PyObject *) inst);

    /* Proceed with object destruction normally. */
}

2.2.7. 更多建议

请记住,您可以忽略其中的大多数Function,在这种情况下,您将提供0作为值。您必须提供的每个Function都有类型定义。它们位于 Python 的源分发版随附的 Python 包含目录的object.h中。

为了了解如何为新数据类型实现任何特定方法,请执行以下操作:下载并解压缩 Python 源代码分发包。转到Objects目录,然后在 C 源文件中搜索tp_以及所需的Function(例如tp_printtp_compare)。您将找到要实现的Function的示例。

当您需要验证对象是否为您要实现的类型的实例时,请使用PyObject_TypeCheck()函数。它的用法示例可能类似于以下内容:

if (! PyObject_TypeCheck(some_object, &MyType)) {
    PyErr_SetString(PyExc_TypeError, "arg #1 not a mything");
    return NULL;
}

Footnotes

  • [1]

    • 当我们知道对象是基本类型(例如字符串或浮点型)时,这是正确的。
  • [2]

    • 在此示例中,我们在tp_dealloc处理程序中依赖于此,因为我们的类型不支持垃圾回收。即使类型支持垃圾回收,也可以进行一些调用来“取消跟踪”垃圾回收中的对象,但是,这些调用是高级的,因此不在此处介绍。
  • [3]

    • 现在我们知道第一个和最后一个成员是字符串,因此也许我们可以减少对它们的引用计数的减少,但是,我们接受字符串子类的实例。即使取消分配普通字符串不会回调到我们的对象中,我们也不能保证取消分配字符串子类的实例不会回调到我们的对象中。
  • [4]

    • 即使在第三版中,我们也不能保证避免循环。允许使用字符串子类的实例,即使普通字符串不允许,字符串子类也可以允许循环。