2.定义扩展类型:教程

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

 

2.1。基础知识

CPython 运行时将所有Python对象视为PyObject*类型的变量,它充当所有Python对象的“基类型”.PyObject结构本身只包含对象的引用计数以及指向对象“类型对象”的指针。这是动作的位置;例如,类型对象确定解释器调用哪个(C)函数,例如,一个属性被查看一个对象,一个被调用的方法,或者它被另一个对象乘以。这些C函数被称为“类型方法”.

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

这种事情只能通过示例来解释,所以这里是一个最小但非完整的模块,它在Cextension模块中定义一个名为Custom的新类型custom

注意

我们在这里展示的是定义static扩展类型的传统方式。它应该适合大多数用途。C API还允许使用PyType_FromSpec()函数,这个教程没有介绍.

#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);
    PyModule_AddObject(m, "Custom", (PyObject *) &CustomType);
    return m;
}

现在有很多东西可以接受,但希望上一章看起来很熟悉。这个文件定义了三件事:

  1. 什么Custom 宾语包含:这是CustomObjectstruct,每个分配一次Custominstance.
  2. 怎么Custom 类型表现:这是CustomTypestruct,它定义了一组标志和函数指针,解释器在请求特定操作时检查它们.
  3. 如何初始化custom模块:这是PyInit_custom函数和相关的custommodule struct.

第一位是:

typedef struct {
    PyObject_HEAD
} CustomObject;

这是Custom对象将包含的内容。PyObject_HEAD在每个对象结构的开头都是必需的,并定义了一个名为ob_base类型PyObject,包含指向类型对象和参考计数的指针(可以使用宏Py_REFCNTPy_TYPE分别)。宏的原因是要删除布局并在调试版本中启用其他字段.

注意

PyObject_HEAD对于意外添加一个警惕:一些编译器会抱怨

当然,对象一般存储除标准之外的其他数据PyObject_HEAD样板;例如,这里是forstandard 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_new = PyType_GenericNew,
};

注意

我们建议使用如上所述的C99样式的指定初始化程序,以避免列出所有PyTypeObject你不关心的字段,也避免关心字段的声明顺序.

PyTypeObject的实际定义object.h多了字段比上面的定义。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 modules.

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

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

注意

如果你希望你的类型可以从Python继承,你的类型有相同的tp_basicsize作为其基本类型,您可能会遇到多重继承问题。你的类型的Python子类必须先在__bases__中列出你的类型,否则它将无法调用你的类型__new__()方法没有出错。您可以通过确保类型具有更大的tp_basicsize比它的基类型。大多数情况下,无论如何都是如此,因为你的基本类型将是object,或者你将数据成员添加到你的基本类型,因此增加其大小.

我们将类标志设置为Py_TPFLAGS_DEFAULT.

.tp_flags = Py_TPFLAGS_DEFAULT,

所有类型都应在其标志中包含此常量。它允许定义所有主题,直到至少Python 3.3。如果你需要更多成员,你需要OR相应的标志.

我们为tp_doc.

.tp_doc = "Custom objects",

中的类型提供一个doc字符串。为了能够创建对象,我们必须提供一个tp_new处理程序。这相当于Python方法__new__(),但是必须明确指定。在这种情况下,我们可以使用API​​函数提供的defaultimplementation PyType_GenericNew().

.tp_new = PyType_GenericNew,

文件中的其他所有内容都应该是熟悉的,除了PyInit_custom()

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

中的一些代码这个初始化Custom类型,将许多成员填入适当的默认值,包括ob_type我们最初设置为NULL.

PyModule_AddObject(m, "Custom", (PyObject *) &CustomType);

这会将类型添加到模块字典中。这允许我们通过调用Custom class:Custom创建

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

实例就是这样!剩下的就是建造它;将上面的代码放在一个名为custom.c的文件中:

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

在一个名为setup.py的文件中;然后打字

$ python setup.py build

在shell中应该在子目录中生成一个文件custom.so;移动整个目录并激活Python – 你应该能够import custom和自定义对象一起玩.

那不是很难,是吗?

当然,目前的Custom类型非常无趣。它没有数据,也没有做任何事情。它甚至不能被子类化.

注意

本文档展示了用于构建C扩展的标准distutils模块,建议在现实世界的用例中使用更新的并且维护得更好setuptools图书馆。有关如何执行此操作的文档超出了本文档的范围,可以在Python Packaging用户指南中找到.

2.2。向Basic示例添加数据和方法

让我们扩展基本示例以添加一些数据和方法。我们也可以将类型用作基类。我们将创建一个新模块,custom2添加这些功能:

#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);
    PyModule_AddObject(m, "Custom", (PyObject *) &CustomType);
    return m;
}

这个版本的模块有很多变化.

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

#include <structmember.h>

这包括提供我们用来处理属性的声明,如稍后所述.

Custom类型现在在其C结构中有三个数据属性,first, lastnumberfirstlast变量是包含名和姓的Pythonstrings。number属性是一个C整数.

对象结构相应更新:

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

分配给tp_dealloc成员:

.tp_dealloc = (destructor) Custom_dealloc,

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

注意

明确演员destructor上面是必需的,因为我们定义Custom_dealloc来取CustomObject *参数,但tp_dealloc函数指针期望接收PyObject *参数。否则,编译器将发出警告。这是面向对象的多态,用C!

我们要确保将名字和姓氏初始化为emptystrings,所以我们提供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 member:

.tp_new = Custom_new,

tp_new处理程序负责创建(而不是初始化)该类型的对象。它在Python中作为__new__()方法公开。它不需要定义一个tp_new成员,实际上很多扩展类型只会重复使用PyType_GenericNew(),如同在Custom上面的类型。在这种情况下,我们使用tp_new处理程序将firstlast属性初始化为非NULL默认值.

tp_new是通过了实例化的类型(不一定是CustomType,如果实例化子类)并且在调用类型时传递任何参数,并且期望返回创建的实例。tp_newhandlersalways接受位置和关键字参数,但它们经常忽略参数,将参数处理留给初始化器(a.k.a。tp_init在C或__init__在Python)方法.

注意

tp_new不应该明确地调用tp_init,因为解释器会自己调用它.

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

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

由于内存分配可能失败,我们必须检查tp_alloc结果NULL在继续之前

注意

我们没有填写tp_alloc插入自己。相反PyType_Ready()通过从我们的基类继承它来填充它,这是object默认情况下。大多数类型使用默认的allocationstrategy .

注意

如果你正在建立一个合作社tp_new(一个调用基类型tp_new__new__()),你必须not尝试在运行时使用方法resolutionorder确定要调用的方法。总是静态地确定你要调用什么类型,并直接调用它tp_new,或者通过type->tp_base->tp_new。如果不这样做,同样继承自其他Python定义的类的yourtype的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;
}

填充tp_init slot.

.tp_init = (initproc) Custom_init,

tp_init slot作为__init__()方法在Python中公开。它用于在创建对象后初始化它。初始化器总是接受位置和关键字参数,并且它们应该在成功时返回0或者在错误时-1返回

tp_new处理程序不同,没有保证tp_init被调用(例如,pickle模块默认不调用__init__()在unpickled实例上)。它也可以多次出现。任何人都可以在对象上调用__init__()方法。因此,在分配新属性值时我们必须格外小心。我们可能会受到诱惑,比如像这样分配first成员:

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

但这样做会有风险。我们的类型不限制first成员的类型,因此它可以是任何类型的对象。它可能有一个atrutructor导致代码被执行,试图访问first成员;或者析构函数可以释放全局解释器锁并让任意代码在访问和修改我们的对象的其他代码中运行.

偏执并保护自己免受这种可能性在减少参考数量之前,我们几乎总是重新分配成员。我们不是必须这样做的吗?

  • 当我们完全知道引用计数大于1时;
  • 当我们知道对象的释放[1]不会释放 GIL 不要再调用我们类型的代码;
  • 当减少一个引用时算在tp_dealloc不支持循环垃圾收集的类型的处理程序[2] .

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

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 slot:

.tp_members = Custom_members,

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

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

我们定义一个方法,Custom.name(),输出对象名称作为名字和姓氏的连接.

static PyObject *
Custom_name(CustomObject *self)
{
    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);
}

该方法实现为一个带有Custom(或Custom子类)实例作为第一个参数。方法总是将实例作为第一个参数。方法通常也采用位置和关键字参数,但在这种情况下我们不采用任何方法,也不需要接受位置参数元组或关键字参数字典。这个方法与Python方法等效:

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

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

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

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 slot:

.tp_methods = Custom_methods,

最后,我们将使我们的类型可用作子类的基类。到目前为止我们已经仔细地编写了我们的方法,以便它们不会对所创建或使用的对象的类型做出任何假设,所以我们需要做的就是添加Py_TPFLAGS_BASETYPE我们的班级标志定义

.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,

我们重命名PyInit_custom()PyInit_custom2(),更新PyModuleDefstruct,并更新PyTypeObjectstruct.

最后,我们更新了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。提供对数据属性的更好控制

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

#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);
    PyModule_AddObject(m, "Custom", (PyObject *) &CustomType);
    return m;
}

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

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对象和一个“closure”,它是一个void指针。在这种情况下,将忽略闭包。(闭包支持高级用法,其中定义数据被传递给getter和setter。例如,这可以用于允许一组getter和setter函数根据闭包中的数据来决定要获取或设置的属性。)

setter函数传递Custom对象,新值和封装。新值可能是NULL,在这种情况下,属性被删除。在我们的setter中,如果删除了属性或者newnew值不是字符串,我们会引发错误.

我们创建一个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 slot:

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

通过这些更改,我们可以确保firstlast成员随时NULL所以我们可以几乎在所有情况下都删除了对NULL值的检查。这意味着大多数Py_XDECREF()调用都可以转换为Py_DECREF()调用。我们唯一无法更改这些调用的地方是tp_dealloc实现,这些成员的初始化可能在tp_new.

中失败了我们还在初始化中重命名模块初始化函数和模块名称函数,就像我们之前做的那样,我们在setup.py file.

2.4中添加了一个额外的定义。支持循环垃圾收集

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

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

在此示例中,我们创建一个包含自身的列表。当我们删除它时,它仍然有自己的引用。它的引用计数不会降到零。幸运的是,Python的循环垃圾收集器最终会发现该列表是垃圾并且是免费的.

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

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

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

#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);
    PyModule_AddObject(m, "Custom", (PyObject *) &CustomType);
    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()function,传递给遍历方法。visit()函数将子对象和额外参数作为参数arg传递给遍历方法。它返回一个整数值,如果它非零,则必须返回.

Python提供了一个自动调用visitfunctions的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;
}

注意

tp_traverse实施必须准确地命名它的参数visitarg为了使用Py_VISIT().

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

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

注意使用Py_CLEAR()宏。建议并安全地清除任意类型的数据属性,同时减少其引用计数。如果你打电话Py_XDECREF()在将其设置为NULL,有可能属性的析构函数会回调到再次读取属性的代码(especially如果有引用循环).

注意

你可以效仿Py_CLEAR()通过写:

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

然而,在删除属性时总是使用Py_CLEAR()更容易且更不容易出错。不要以牺牲鲁棒性为代价进行微优化!

解析器Custom_dealloc在清除属性时可能会调用任意代码。这意味着可以在函数内部触发循环GC。由于GC假定引用计数不为零,我们需要在清除成员之前通过调用PyObject_GC_UnTrack()来解除GC中的对象。这是我们重新实现的deallocator使用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结构可能很困难.

在这个例子中我们将创建一个SubList类型继承自内置的list类型。新类型将与常规列表完全兼容,但是会有一个额外的increment()方法增加内部计数器:

>>> import sublist
>>> s = sublist.SubList(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;
} 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);
    PyModule_AddObject(m, "SubList", (PyObject *) &SubListType);
    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__basetype的方法.

使用自定义tp_newtp_dealloc成员。tp_new处理程序不应该用tp_alloc,但让基类通过调用自己的tp_new.

PyTypeObjectstruct支持tp_base指定类型的具体基类。由于跨平台编译问题,您无法直接填写该字段并引用PyList_Type;它应该在模块初始化函数的后期完成:

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);
    PyModule_AddObject(m, "SubList", (PyObject *) &SubListType);
    return m;
}

在调用PyType_Ready()之前,类型结构必须有tp_base当我们驱动现有类型时,没有必要用tp_alloc填写PyType_GenericNew()插槽 – 来自basetype的分配函数将被继承.

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

Footnotes

[1] 当我们知道对象是一个基本类型,如一个字符串或漂浮时,这是真的.
[2] 我们依赖于tp_dealloc处理器在这个例子中,因为我们的类型不支持垃圾收集.
[3] 我们现在知道第一个和最后一个成员是字符串,所以也许我们可以减少它们的引用计数,但是,我们接受字符串子类的实例。即使解除分配正常字符串不会回调到我们的对象,我们也不能保证字符串子类的deallocatingan实例不会回调到我们的对象中.
[4] 另外,即使我们的属性仅限于字符串实例,用户也可以传递任意的str子类,因此仍然可以创建参考周期.

评论被关闭。