python / 3.7.2rc1 / all / extending-newtypes_tutorial.html

2. 定义扩展类型:教程

Python 允许 C 扩展模块的编写者定义可以从 Python 代码操纵的新类型,就像内置的strlist类型一样。所有扩展类型的代码都遵循一种模式,但是在开始之前,您需要了解一些详细信息。本文档是对该主题的简要介绍。

2.1. 基础

CPython运行时将所有 Python 对象视为PyObject*类型的变量,该变量充当所有 Python 对象的“基本类型”。 PyObject结构本身仅包含对象的reference count和指向该对象的“类型对象”的指针。这是行动所在;类型对象确定当(例如)在某个对象上查找某个属性,一种调用的方法或该属性与另一个对象相乘时,解释器将调用哪些(C)函数。这些 C 函数称为“类型方法”。

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

这种事情只能pass示例来解释,所以这是一个最小但完整的模块,它在 C 扩展模块custom内定义了一个名为Custom的新类型:

Note

我们在这里显示的是定义* static *扩展类型的传统方式。对于大多数用途来说应该足够了。 C API 还允许使用PyType_FromSpec()函数定义堆分配的扩展类型,本教程中没有介绍。

#define PY_SSIZE_T_CLEAN
#include <Python.h>

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

static PyTypeObject CustomType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "custom.Custom",
    .tp_doc = "Custom objects",
    .tp_basicsize = sizeof(CustomObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT,
    .tp_new = PyType_GenericNew,
};

static PyModuleDef custommodule = {
    PyModuleDef_HEAD_INIT,
    .m_name = "custom",
    .m_doc = "Example module that creates an extension type.",
    .m_size = -1,
};

PyMODINIT_FUNC
PyInit_custom(void)
{
    PyObject *m;
    if (PyType_Ready(&CustomType) < 0)
        return NULL;

    m = PyModule_Create(&custommodule);
    if (m == NULL)
        return NULL;

    Py_INCREF(&CustomType);
    if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {
        Py_DECREF(&CustomType);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}

现在可以同时进行很多工作,但是希望从上一章开始就可以很熟悉了。该文件定义了三件事:

  • Custom 对象 包含的内容:这是CustomObject结构,为每个Custom实例分配一次。

  • Custom type 的行为方式:这是CustomType结构,它定义了一组标志和函数指针,解释器在请求特定操作时会检查这些标志和函数指针。

  • 如何初始化custom模块:这是PyInit_custom函数和关联的custommodule结构。

第一位是:

typedef struct {
    PyObject_HEAD
} CustomObject;

这就是定制对象将包含的内容。 PyObject_HEAD在每个对象 struct 的开头都是必需的,并定义了一个名为ob_base的类型PyObject的字段,其中包含指向类型对象的指针和引用计数(可以分别使用宏Py_REFCNTPy_TYPE进行访问)。宏的原因是要抽象出布局并在调试版本中启用其他字段。

Note

PyObject_HEAD宏之后的上方没有分号。小心添加一个意外:有些编译器会抱怨。

当然,除了标准的PyObject_HEAD样板外,对象通常还存储其他数据。例如,以下是标准 Python 浮点数的定义:

typedef struct {
    PyObject_HEAD
    double ob_fval;
} PyFloatObject;

第二位是类型对象的定义。

static PyTypeObject CustomType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "custom.Custom",
    .tp_doc = "Custom objects",
    .tp_basicsize = sizeof(CustomObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT,
    .tp_new = PyType_GenericNew,
};

Note

我们建议如上所述使用 C99 样式的指定初始值设定项,以避免列出您不需要的所有PyTypeObject字段,也避免关心字段的语句 Sequences。

object.h中的PyTypeObject的实际定义比上面的定义多了fields。其余字段将由 C 编译器填充为零,除非您需要,否则通常不明确指定它们。

我们将把它分开,一次一个字段:

PyVarObject_HEAD_INIT(NULL, 0)

该行是初始化上面提到的ob_base字段的必填样板。

.tp_name = "custom.Custom",

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

>>> "" + custom.Custom()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "custom.Custom") to str

请注意,该名称是点名,包括模块名称和模块内类型的名称。在这种情况下,模块为custom,类型为Custom,因此我们将类型名称设置为custom.Custom。使用真实的点导入路径对于使您的类型与pydocpickle模块兼容非常重要。

.tp_basicsize = sizeof(CustomObject),
.tp_itemsize = 0,

这样一来,Python 就会知道在创建新的Custom实例时要分配多少内存。 tp_itemsize仅用于可变大小的对象,否则应为零。

Note

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

我们将类标志设置为Py_TPFLAGS_DEFAULT

.tp_flags = Py_TPFLAGS_DEFAULT,

所有类型都应在其标志中包含此常量。它启用了至少在 Python 3.3 之前定义的所有成员。如果需要其他成员,则需要对相应的标志进行或。

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

.tp_doc = "Custom objects",

要启用对象创建,我们必须提供一个tp_new处理程序。这等效于 Python 方法new(),但必须显式指定。在这种情况下,我们只能使用 API 函数PyType_GenericNew()提供的默认实现。

.tp_new = PyType_GenericNew,

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

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

这将初始化Custom类型,将许多成员填充为适当的默认值,包括我们最初设置为NULLob_type

Py_INCREF(&CustomType);
if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {
    Py_DECREF(&CustomType);
    Py_DECREF(m);
    return NULL;
}

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

>>> import custom
>>> mycustom = custom.Custom()

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

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

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

$ python setup.py build

在 shell 上应在子目录中产生文件custom.so;移至该目录并启动 Python-您应该能够import custom并使用“自定义”对象。

那不是那么难,不是吗?

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

Note

尽管本文档展示了用于构建 C 扩展的标准distutils模块,但在实际使用案例中建议使用更新更好的setuptools库。有关如何执行此操作的文档超出了本文档的范围,可以在Python 打包用户指南中找到。

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

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

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

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

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

static PyObject *
Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    CustomObject *self;
    self = (CustomObject *) type->tp_alloc(type, 0);
    if (self != NULL) {
        self->first = PyUnicode_FromString("");
        if (self->first == NULL) {
            Py_DECREF(self);
            return NULL;
        }
        self->last = PyUnicode_FromString("");
        if (self->last == NULL) {
            Py_DECREF(self);
            return NULL;
        }
        self->number = 0;
    }
    return (PyObject *) self;
}

static int
Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"first", "last", "number", NULL};
    PyObject *first = NULL, *last = NULL, *tmp;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,
                                     &first, &last,
                                     &self->number))
        return -1;

    if (first) {
        tmp = self->first;
        Py_INCREF(first);
        self->first = first;
        Py_XDECREF(tmp);
    }
    if (last) {
        tmp = self->last;
        Py_INCREF(last);
        self->last = last;
        Py_XDECREF(tmp);
    }
    return 0;
}

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

static PyObject *
Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))
{
    if (self->first == NULL) {
        PyErr_SetString(PyExc_AttributeError, "first");
        return NULL;
    }
    if (self->last == NULL) {
        PyErr_SetString(PyExc_AttributeError, "last");
        return NULL;
    }
    return PyUnicode_FromFormat("%S %S", self->first, self->last);
}

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

static PyTypeObject CustomType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "custom2.Custom",
    .tp_doc = "Custom objects",
    .tp_basicsize = sizeof(CustomObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
    .tp_new = Custom_new,
    .tp_init = (initproc) Custom_init,
    .tp_dealloc = (destructor) Custom_dealloc,
    .tp_members = Custom_members,
    .tp_methods = Custom_methods,
};

static PyModuleDef custommodule = {
    PyModuleDef_HEAD_INIT,
    .m_name = "custom2",
    .m_doc = "Example module that creates an extension type.",
    .m_size = -1,
};

PyMODINIT_FUNC
PyInit_custom2(void)
{
    PyObject *m;
    if (PyType_Ready(&CustomType) < 0)
        return NULL;

    m = PyModule_Create(&custommodule);
    if (m == NULL)
        return NULL;

    Py_INCREF(&CustomType);
    if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {
        Py_DECREF(&CustomType);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}

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

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

#include <structmember.h>

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

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

对象结构将相应更新:

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

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

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

分配给tp_dealloc成员:

.tp_dealloc = (destructor) Custom_dealloc,

此方法首先清除两个 Python 属性的引用计数。 Py_XDECREF()正确处理了其参数为NULL的情况(如果tp_new中途失败,可能会在这里发生)。然后,它调用对象类型的tp_free成员(由Py_TYPE(self)计算)以释放对象的内存。请注意,对象的类型可能不是CustomType,因为对象可能是子类的实例。

Note

由于我们定义Custom_dealloc接受CustomObject *参数,因此需要显式转换为destructor,但是tp_dealloc函数指针希望接收PyObject *参数。否则,编译器将发出警告。这是 C 语言中的面向对象的多态性!

我们要确保名字和姓氏都初始化为空字符串,因此我们提供了tp_new实现:

static PyObject *
Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    CustomObject *self;
    self = (CustomObject *) type->tp_alloc(type, 0);
    if (self != NULL) {
        self->first = PyUnicode_FromString("");
        if (self->first == NULL) {
            Py_DECREF(self);
            return NULL;
        }
        self->last = PyUnicode_FromString("");
        if (self->last == NULL) {
            Py_DECREF(self);
            return NULL;
        }
        self->number = 0;
    }
    return (PyObject *) self;
}

并将其安装在tp_new成员中:

.tp_new = Custom_new,

tp_new处理程序负责创建(而不是初始化)该类型的对象。它在 Python 中以new()方法公开。不需要定义tp_new成员,并且实际上许多扩展类型将像上面的Custom类型的第一个版本那样简单地重用PyType_GenericNew()。在这种情况下,我们使用tp_new处理程序将firstlast属性初始化为非NULL默认值。

传递了tp_new实例化的类型(如果实例化了一个子类,则不一定是CustomType)以及调用该类型时传递的任何参数,并且期望该参数返回创建的实例。 tp_new处理程序始终接受位置和关键字参数,但它们通常会忽略参数,而将参数处理留给初始化方法(在 C 中为tp_init或在 Python 中为__init__)。

Note

tp_new不应显式调用tp_init,因为解释器会自己执行。

tp_new实现调用tp_alloc插槽分配内存:

self = (CustomObject *) type->tp_alloc(type, 0);

由于内存分配可能会失败,因此我们必须先针对NULL检查tp_alloc的结果,然后再 continue。

Note

我们没有自己填写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
Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"first", "last", "number", NULL};
    PyObject *first = NULL, *last = NULL, *tmp;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,
                                     &first, &last,
                                     &self->number))
        return -1;

    if (first) {
        tmp = self->first;
        Py_INCREF(first);
        self->first = first;
        Py_XDECREF(tmp);
    }
    if (last) {
        tmp = self->last;
        Py_INCREF(last);
        self->last = last;
        Py_XDECREF(tmp);
    }
    return 0;
}

pass填充tp_init插槽。

.tp_init = (initproc) Custom_init,

tp_init插槽在 Python 中以init()方法公开。创建对象后,它用于初始化对象。初始化程序始终接受位置和关键字参数,并且在成功时应返回0,在错误时应返回-1

tp_new处理程序不同,根本无法保证会调用tp_init(例如,默认情况下pickle模块不会在未拾取实例上调用init())。也可以多次调用。任何人都可以在我们的对象上调用init()方法。因此,在分配新属性值时,我们必须格外小心。例如,我们可能会很想分配first成员:

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

但这是有风险的。我们的类型不限制first成员的类型,因此它可以是任何类型的对象。它可能有一个析构函数,导致执行试图访问first成员的代码。否则该析构函数可以释放GlobalInterpreter 锁并让任意代码在其他访问和修改我们对象的线程中运行。

为了保持偏执并保护自己免受这种可能性的困扰,我们几乎总是在减少其引用计数之前重新分配成员。我们什么时候不必这样做?

  • 当我们完全知道参考计数大于 1 时;

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

  • 在不支持循环垃圾回收[2]的类型上的tp_dealloc处理程序中减少引用计数时。

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

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

并将定义放在tp_members插槽中:

.tp_members = Custom_members,

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

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

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

static PyObject *
Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))
{
    if (self->first == NULL) {
        PyErr_SetString(PyExc_AttributeError, "first");
        return NULL;
    }
    if (self->last == NULL) {
        PyErr_SetString(PyExc_AttributeError, "last");
        return NULL;
    }
    return PyUnicode_FromFormat("%S %S", self->first, self->last);
}

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

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

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

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

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

(请注意,我们使用METH_NOARGS标志来表示该方法除了* self *之外没有其他参数)

并将其分配给tp_methods插槽:

.tp_methods = Custom_methods,

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

.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,

我们将PyInit_custom()重命名为PyInit_custom2(),在PyModuleDef结构中更新模块名称,并在PyTypeObject结构中更新完整的类名。

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

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

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

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

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

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

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

static PyObject *
Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    CustomObject *self;
    self = (CustomObject *) type->tp_alloc(type, 0);
    if (self != NULL) {
        self->first = PyUnicode_FromString("");
        if (self->first == NULL) {
            Py_DECREF(self);
            return NULL;
        }
        self->last = PyUnicode_FromString("");
        if (self->last == NULL) {
            Py_DECREF(self);
            return NULL;
        }
        self->number = 0;
    }
    return (PyObject *) self;
}

static int
Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"first", "last", "number", NULL};
    PyObject *first = NULL, *last = NULL, *tmp;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,
                                     &first, &last,
                                     &self->number))
        return -1;

    if (first) {
        tmp = self->first;
        Py_INCREF(first);
        self->first = first;
        Py_DECREF(tmp);
    }
    if (last) {
        tmp = self->last;
        Py_INCREF(last);
        self->last = last;
        Py_DECREF(tmp);
    }
    return 0;
}

static PyMemberDef Custom_members[] = {
    {"number", T_INT, offsetof(CustomObject, number), 0,
     "custom number"},
    {NULL}  /* Sentinel */
};

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

static int
Custom_setfirst(CustomObject *self, PyObject *value, void *closure)
{
    PyObject *tmp;
    if (value == NULL) {
        PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");
        return -1;
    }
    if (!PyUnicode_Check(value)) {
        PyErr_SetString(PyExc_TypeError,
                        "The first attribute value must be a string");
        return -1;
    }
    tmp = self->first;
    Py_INCREF(value);
    self->first = value;
    Py_DECREF(tmp);
    return 0;
}

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

static int
Custom_setlast(CustomObject *self, PyObject *value, void *closure)
{
    PyObject *tmp;
    if (value == NULL) {
        PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");
        return -1;
    }
    if (!PyUnicode_Check(value)) {
        PyErr_SetString(PyExc_TypeError,
                        "The last attribute value must be a string");
        return -1;
    }
    tmp = self->last;
    Py_INCREF(value);
    self->last = value;
    Py_DECREF(tmp);
    return 0;
}

static PyGetSetDef Custom_getsetters[] = {
    {"first", (getter) Custom_getfirst, (setter) Custom_setfirst,
     "first name", NULL},
    {"last", (getter) Custom_getlast, (setter) Custom_setlast,
     "last name", NULL},
    {NULL}  /* Sentinel */
};

static PyObject *
Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))
{
    return PyUnicode_FromFormat("%S %S", self->first, self->last);
}

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

static PyTypeObject CustomType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "custom3.Custom",
    .tp_doc = "Custom objects",
    .tp_basicsize = sizeof(CustomObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
    .tp_new = Custom_new,
    .tp_init = (initproc) Custom_init,
    .tp_dealloc = (destructor) Custom_dealloc,
    .tp_members = Custom_members,
    .tp_methods = Custom_methods,
    .tp_getset = Custom_getsetters,
};

static PyModuleDef custommodule = {
    PyModuleDef_HEAD_INIT,
    .m_name = "custom3",
    .m_doc = "Example module that creates an extension type.",
    .m_size = -1,
};

PyMODINIT_FUNC
PyInit_custom3(void)
{
    PyObject *m;
    if (PyType_Ready(&CustomType) < 0)
        return NULL;

    m = PyModule_Create(&custommodule);
    if (m == NULL)
        return NULL;

    Py_INCREF(&CustomType);
    if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {
        Py_DECREF(&CustomType);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}

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

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

static int
Custom_setfirst(CustomObject *self, PyObject *value, void *closure)
{
    PyObject *tmp;
    if (value == NULL) {
        PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");
        return -1;
    }
    if (!PyUnicode_Check(value)) {
        PyErr_SetString(PyExc_TypeError,
                        "The first attribute value must be a string");
        return -1;
    }
    tmp = self->first;
    Py_INCREF(value);
    self->first = value;
    Py_DECREF(tmp);
    return 0;
}

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

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

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

static PyGetSetDef Custom_getsetters[] = {
    {"first", (getter) Custom_getfirst, (setter) Custom_setfirst,
     "first name", NULL},
    {"last", (getter) Custom_getlast, (setter) Custom_setlast,
     "last name", NULL},
    {NULL}  /* Sentinel */
};

并将其注册在tp_getset插槽中:

.tp_getset = Custom_getsetters,

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

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

static PyMemberDef Custom_members[] = {
    {"number", T_INT, offsetof(CustomObject, number), 0,
     "custom number"},
    {NULL}  /* Sentinel */
};

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

static int
Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"first", "last", "number", NULL};
    PyObject *first = NULL, *last = NULL, *tmp;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,
                                     &first, &last,
                                     &self->number))
        return -1;

    if (first) {
        tmp = self->first;
        Py_INCREF(first);
        self->first = first;
        Py_DECREF(tmp);
    }
    if (last) {
        tmp = self->last;
        Py_INCREF(last);
        self->last = last;
        Py_DECREF(tmp);
    }
    return 0;
}

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

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

2.4. 支持循环垃圾收集

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

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

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

Custom示例的第二个版本中,我们允许将任何类型的对象存储在firstlast属性[4]中。此外,在第二版和第三版中,我们允许子类Custom,并且子类可以添加任意属性。由于以下两个原因中的任何一个,Custom对象可以参与循环:

>>> import custom3
>>> class Derived(custom3.Custom): pass
...
>>> n = Derived()
>>> n.some_attribute = n

为了允许循环 GC 正确检测和收集参与参考周期的Custom实例,我们的Custom类型需要填充另外两个插槽并启用一个启用这些插槽的标志:

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

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

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

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

static void
Custom_dealloc(CustomObject *self)
{
    PyObject_GC_UnTrack(self);
    Custom_clear(self);
    Py_TYPE(self)->tp_free((PyObject *) self);
}

static PyObject *
Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    CustomObject *self;
    self = (CustomObject *) type->tp_alloc(type, 0);
    if (self != NULL) {
        self->first = PyUnicode_FromString("");
        if (self->first == NULL) {
            Py_DECREF(self);
            return NULL;
        }
        self->last = PyUnicode_FromString("");
        if (self->last == NULL) {
            Py_DECREF(self);
            return NULL;
        }
        self->number = 0;
    }
    return (PyObject *) self;
}

static int
Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"first", "last", "number", NULL};
    PyObject *first = NULL, *last = NULL, *tmp;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,
                                     &first, &last,
                                     &self->number))
        return -1;

    if (first) {
        tmp = self->first;
        Py_INCREF(first);
        self->first = first;
        Py_DECREF(tmp);
    }
    if (last) {
        tmp = self->last;
        Py_INCREF(last);
        self->last = last;
        Py_DECREF(tmp);
    }
    return 0;
}

static PyMemberDef Custom_members[] = {
    {"number", T_INT, offsetof(CustomObject, number), 0,
     "custom number"},
    {NULL}  /* Sentinel */
};

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

static int
Custom_setfirst(CustomObject *self, PyObject *value, void *closure)
{
    if (value == NULL) {
        PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");
        return -1;
    }
    if (!PyUnicode_Check(value)) {
        PyErr_SetString(PyExc_TypeError,
                        "The first attribute value must be a string");
        return -1;
    }
    Py_INCREF(value);
    Py_CLEAR(self->first);
    self->first = value;
    return 0;
}

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

static int
Custom_setlast(CustomObject *self, PyObject *value, void *closure)
{
    if (value == NULL) {
        PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");
        return -1;
    }
    if (!PyUnicode_Check(value)) {
        PyErr_SetString(PyExc_TypeError,
                        "The last attribute value must be a string");
        return -1;
    }
    Py_INCREF(value);
    Py_CLEAR(self->last);
    self->last = value;
    return 0;
}

static PyGetSetDef Custom_getsetters[] = {
    {"first", (getter) Custom_getfirst, (setter) Custom_setfirst,
     "first name", NULL},
    {"last", (getter) Custom_getlast, (setter) Custom_setlast,
     "last name", NULL},
    {NULL}  /* Sentinel */
};

static PyObject *
Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))
{
    return PyUnicode_FromFormat("%S %S", self->first, self->last);
}

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

static PyTypeObject CustomType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "custom4.Custom",
    .tp_doc = "Custom objects",
    .tp_basicsize = sizeof(CustomObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
    .tp_new = Custom_new,
    .tp_init = (initproc) Custom_init,
    .tp_dealloc = (destructor) Custom_dealloc,
    .tp_traverse = (traverseproc) Custom_traverse,
    .tp_clear = (inquiry) Custom_clear,
    .tp_members = Custom_members,
    .tp_methods = Custom_methods,
    .tp_getset = Custom_getsetters,
};

static PyModuleDef custommodule = {
    PyModuleDef_HEAD_INIT,
    .m_name = "custom4",
    .m_doc = "Example module that creates an extension type.",
    .m_size = -1,
};

PyMODINIT_FUNC
PyInit_custom4(void)
{
    PyObject *m;
    if (PyType_Ready(&CustomType) < 0)
        return NULL;

    m = PyModule_Create(&custommodule);
    if (m == NULL)
        return NULL;

    Py_INCREF(&CustomType);
    if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {
        Py_DECREF(&CustomType);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}

首先,遍历方法让循环 GC 知道可能参与循环的子对象:

static int
Custom_traverse(CustomObject *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 提供了一个Py_VISIT()宏,该宏可以自动调用访问函数。使用Py_VISIT(),我们可以最小化Custom_traverse中的样板数量:

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

Note

tp_traverse实现必须使用* visit arg *精确命名其参数,才能使用Py_VISIT()

其次,我们需要提供一种清除所有可能参与循环的子对象的方法:

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

请注意Py_CLEAR()宏的使用。推荐的一种安全的方法是清除任意类型的数据属性,同时减少其引用计数。如果要在将其设置为NULL之前在该属性上调用Py_XDECREF(),则该属性的析构函数可能会回调到再次读取该属性的代码中(特别是,如果有参考循环)。

Note

您可以pass编写以下内容来模拟Py_CLEAR()

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

但是,在删除属性时始终使用Py_CLEAR()会更容易且更容易出错。不要试图以健壮性为代价进行微优化!

清除属性时,解除分配器Custom_dealloc可以调用任意代码。这意味着可以在函数内部触发循环 GC。由于 GC 假定引用计数不为零,因此我们需要在清除成员之前pass调用PyObject_GC_UnTrack()来从 GC 取消跟踪对象。这是我们使用PyObject_GC_UnTrack()Custom_clear重新实现的释放器:

static void
Custom_dealloc(CustomObject *self)
{
    PyObject_GC_UnTrack(self);
    Custom_clear(self);
    Py_TYPE(self)->tp_free((PyObject *) self);
}

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

.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,

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

2.5. 子类化其他类型

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

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

>>> import sublist
>>> s = sublist.SubList(range(3))
>>> s.extend(s)
>>> print(len(s))
6
>>> print(s.increment())
1
>>> print(s.increment())
2
#define PY_SSIZE_T_CLEAN
#include <Python.h>

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

static PyObject *
SubList_increment(SubListObject *self, PyObject *unused)
{
    self->state++;
    return PyLong_FromLong(self->state);
}

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

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

static PyTypeObject SubListType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "sublist.SubList",
    .tp_doc = "SubList objects",
    .tp_basicsize = sizeof(SubListObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
    .tp_init = (initproc) SubList_init,
    .tp_methods = SubList_methods,
};

static PyModuleDef sublistmodule = {
    PyModuleDef_HEAD_INIT,
    .m_name = "sublist",
    .m_doc = "Example module that creates an extension type.",
    .m_size = -1,
};

PyMODINIT_FUNC
PyInit_sublist(void)
{
    PyObject *m;
    SubListType.tp_base = &PyList_Type;
    if (PyType_Ready(&SubListType) < 0)
        return NULL;

    m = PyModule_Create(&sublistmodule);
    if (m == NULL)
        return NULL;

    Py_INCREF(&SubListType);
    if (PyModule_AddObject(m, "SubList", (PyObject *) &SubListType) < 0) {
        Py_DECREF(&SubListType);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}

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

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

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

当 Python 对象是SubList实例时,其PyObject *指针可以安全地转换为PyListObject *SubListObject *

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

我们在上面看到了如何调用基本类型的__init__方法。

当编写具有自定义tp_newtp_dealloc成员的类型时,此模式很重要。 tp_new处理程序实际上不应为其对象tp_alloc创建对象的内存,而应让 Base Class pass调用其自己的tp_new处理该内存。

PyTypeObject结构支持tp_base,用于指定类型的具体 Base Class。由于跨平台的编译器问题,您不能直接使用PyList_Type来填充该字段;稍后应在模块初始化Function中完成:

PyMODINIT_FUNC
PyInit_sublist(void)
{
    PyObject* m;
    SubListType.tp_base = &PyList_Type;
    if (PyType_Ready(&SubListType) < 0)
        return NULL;

    m = PyModule_Create(&sublistmodule);
    if (m == NULL)
        return NULL;

    Py_INCREF(&SubListType);
    if (PyModule_AddObject(m, "SubList", (PyObject *) &SubListType) < 0) {
        Py_DECREF(&SubListType);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}

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

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

Footnotes

  • [1]

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

    • 在此示例中,我们在tp_dealloc处理程序中依赖于此,因为我们的类型不支持垃圾回收。
  • [3]

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

    • 同样,即使我们的属性仅限于字符串实例,用户也可以传递任意的str子类,因此仍然可以创建引用循环。