定义扩展类型(翻译)

Posted 银魔术师

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了定义扩展类型(翻译)相关的知识,希望对你有一定的参考价值。

Python允许C扩展模块的编写者定义可以从Python代码操作的新类型,就像内置类型str和list类型一样。所有扩展类型的代码都遵循一种模式,但在开始之前需要了解一些细节。

基础

CPython运行时看到所有Python变量的对象类型都是PyObject*(所有Python对象的基类)。PyObject结构本身只包含该对象的引用计数和一个指向该对象的"类型对象"。类型对象决定了解释器调用哪个(C)函数,例如在对象上查找属性、调用方法或者将其与另一个对象相乘,这些C函数被称为"类型方法"。

所以如果你想定义一个新的扩展类型,你需要创建一个新的类型对象。

这种事情只能用示例来解释,下面例子麻雀虽小五脏俱全,它在C扩展模块custom中定义了一个名为Custom的新类型:

注意:我们在这里展示的是定义静态扩展类型的传统方式,适用于大多数用途。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;
}

这个文件定义了三件事情:

  • Custom对象包含什么:定义了CustomObject结构,每个Custom实例分配一次。

  • Custom类型有哪些操作:定义了CustomTypestruct结构,包含一组标志和函数指针。

  • 如何初始化custom模块:定义了PyInit_custom函数和相关的custommodule结构。

第一点是对象的定义:

typedef struct {
    PyObject_HEAD
} CustomObject;

这就是Custom对象将包含的内容。PyObject_HEAD是每个对象结构开始时必需的,它定义了一个PyObject类型的ob_base字段,其中包含一个类型对象的指针和引用计数(这些可以分别使用宏Py_REFCNT和Py_TYPE来访问),使用宏的原因是为了抽象出布局,并在调试版本中启用其他字段。

注意: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_new = PyType_GenericNew,
};

注意:我们推荐使用C99风格的指定初始化器,以避免列出PyTypeObject您不关心的所有字段,并避免关注字段的声明顺序。

object.hPyTypeObject的实际定义包含更多字段,其余字段将由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,使用这种格式作为导入路径让您的类型和pydoc与pickle模块具有良好的兼容性。

.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,

所有类型都应该在其标志中包含这个常量,如果你需要更多的成员,你需要or上相应标志。

我们为该类型提供文档字符串tp_doc。

.tp_doc = "Custom objects",

要启用对象创建,我们必须提供一个tp_new函数,这相当于Python方法__new__(),这边我们可以使用默认实现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类来创建实例:

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

现在只剩下构建它了,将上面的代码保存为custom.c,然后再创建一个setup.py文件,内容如下:

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

接着开始构建

$ python setup.py build

构建结构后会在子目录会产生一个custom.so文件,切换到该目录并启动Python,你就可以执行import custom,然后开始玩转Custom对象了。

注意:虽然本文档展示了distutils构建C扩展的标准模块,但在现实世界的用例中建议使用更新且维护性更好的setuptools库。关于如何做到这一点的文档超出了本文的范围,可以在Python Packaging用户指南中找到。

添加数据和方法

让我们扩展基本示例以添加一些数据和方法,也让它可以作为其他类型的基类使用,我们将创建一个新模块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、last和number。first和last变量是用于保存姓氏和名字的Python字符串,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中途失败,可能会发生这种情况)。然后它调用类型对象(使用Py_TYPE(self)计算)的成员函数tp_free来释放对象的内存。请注意,该对象的类型并不一定是CustomType,因为该对象可能是一个子类的实例。

注意:因为我们定义了Custom_dealloc来接受一个CustomObject 参数,但是tp_dealloc(tp_free)函数指针希望接收一个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,可以直接复用默认实现PyType_GenericNew()。这边我们定义tp_new是为了将first和last属性的默认值初始化为非空。

tp_new在类型实例化时被传递(不一定CustomType,如果被实例化的是子类),接收调用时传递的任何参数,并返回创建的实例。 tp_new总是忽略接收的位置和关键字参数,将参数处理留给初始化方法(C中的tp_init或Python中的__init__)。

注意:tp_new不会显示调用tp_init,Python解释器会做。

tp_new方法通过调用tp_alloc来分配内存:

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

由于内存分配可能会失败,所以我们必须检查tp_alloc的调用结果是否为NULL。

注意:我们并没有设置tp_alloc,而是PyType_Ready()通过继承基类object的默认值来填充它,大多数类型都使用默认分配策略。

注意:如果你正在创建一个调用基类型的tp_new或者__new__()tp_new时,你不能试图在运行时使用方法解析来确定要调用的方法,你必须静态的确定你要调用的类型,然后直接调用类型的tp_new方法或使用type->tp_base->tp_new方式调用。如果你不这样做,你的类型的Python子类也可能无法正常工作。

我们还定义了一个初始化函数,它接受参数来为我们的实例提供初始值:

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成员:

.tp_init = (initproc) Custom_init,

tp_init成员在Python中被导出为__init__()方法,它用于在创建对象后对其进行初始化。初始化函数接受位置和关键字参数,并且在成功时返回0错误时返回-1。

tp_new不同,并不能完全保证tp_init被调用(例如pickle模块默认情况下不会调用unpickled实例的__init__()方法),也不能保证只被调用一次,在我们的对象上任何人都可以调用__init__()方法。出于这个原因,在分配新的属性值时我们必须格外小心,例如像这样分配first成员:

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

这会有风险,我们的类型不限制first成员的类型,所以它可以是任何类型的对象。它可能有一个析构函数,执行一段试图访问该first成员的代码,或者析构函数会释放全局解释器锁,并让其他线程中的任意代码访问和修改我们的对象。

为了避免这种可能性,我们总是在减少引用之前重新分配成员。什么时候我们不必这样做?

  • 当我们完全知道引用计数大于1时;
  • 当我们知道释放对象既不会释放GIL也不会导致任何回调到我们类型的代码中;
  • 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,

每个成员定义都有成员名称,类型,偏移量,访问标志和文档字符串。

这种方法的一个缺点就是没法限制分配给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);
}

该方法实现为一个C函数,它将一个Custom(或 Custom子类)实例作为第一个参数。方法总是以实例作为第一个参数。方法也经常使用位置和关键字参数,但在这种情况下,我们不接受任何参数,也不需要接受位置参数元组或关键字参数字典。该方法等同于Python方法:

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

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

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

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

(请注意,我们使用该METH_NOARGS标志来指示该方法除了sefl以外没有其他参数)

然后设置tp_methods成员:

.tp_methods = Custom_methods,

最后我们允许我们的类型可以派生子类,我们需要做的就是添加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"]),
         ])

更好地控制数据属性

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

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

为了提供更好的控制first和last属性,我们将使用自定义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",这是一个空指针,当前示例中closure将被忽略。(closure是支持将定义数据传递给getter和setter的高级用法,例如允许一组getter和setter函数根据数据决定要获取或设置的属性)

setter函数传递Custom对象、新值和closure,新值可能为NULL,当前示例中属性将被删除。在我们的setter中,如果属性被删除或者它的新值不是字符串,我们会引发错误。

我们创建一个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结构中的最后一项是上面提到的"closure",当前示例中我们不使用闭包,所以设置为NULL,然后我移除这些属性的成员定义:

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

我们还需要更新tp_init来实现仅允许传递字符串:

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

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

支持循环垃圾收集

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

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

在这个例子中,我们创建一个包含它自己的列表。当我们删除它时,它仍然有自己的参考,其引用计数不会降至零。幸运的是Python的循环垃圾回收器最终会发现该列表是垃圾并释放它。

在Custom示例的第二个版本中,我们允许任何类型的对象存储在first或last属性中。此外在第二和第三个版本中,我们允许子类化 Custom,并且子类可以添加任意属性,由于这两个原因之一,Custom对象可能引起循环:

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

为了让陷入循环的Custom实例能够被循环GC正确检测和收集,我们的Custom类型需要填充两个附加的插槽并启用这些插槽:

#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()函数。visit()函数将子对象和arg作为参数传递给遍历方法,它必须返回一个整数值。

Python提供了一个可以自动调用visit函数的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实现必须将其参数命名为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(),那么该属性的析构函数可能会再次调用回这段代码,再次读取该属性的代码(特别是在存在引用循环的情况下)。

注意:Py_CLEAR()的参考实现:

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

尽管如此,删除属性时使用Py_CLEAR()使得代码变得简单,也更不容易出错。不要试图以牺牲稳定性为代价来进行微观优化!

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

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,

子类化其他类型

使用内置类型继承的方式创建从现有类型派生的新扩展类型是最容易的,因为扩展可以轻松使用它所需的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
#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__方法。使用自定义tp_newtp_dealloc成员编写类型时,这种方式非常重要。派生类型的tp_new不应调用tp_alloc来创建对象的内存,应该让其基类通过调用tp_new方法来处理。

PyTypeObject结构支持使用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成员。当我们从现有类型派生时,不需要使用PyType_GenericNew()填充tp_alloc,这个值将自动继承基类型的。

以上是关于定义扩展类型(翻译)的主要内容,如果未能解决你的问题,请参考以下文章

getSupportFragmentManager() 在活动扩展片段中未定义

如何将此 JavaScript 代码片段翻译成 Parenscript?

在代码片段中包含类型转换

VS Code中自定义Emmet代码片段

VSCode插件开发全攻略代码片段设置自定义欢迎页

VS中添加自定义代码片段——偷懒小技巧