(二)定义扩展类型(翻译)

Python容许C扩展模块的编写者定义能够从Python代码操做的新类型,就像内置类型str和list类型同样。全部扩展类型的代码都遵循一种模式,但在开始以前须要了解一些细节。python

基础

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

因此若是你想定义一个新的扩展类型,你须要建立一个新的类型对象。安全

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

注意:咱们在这里展现的是定义静态扩展类型的传统方式,适用于大多数用途。C API还容许使用PyType_FromSpec()函数定义堆分配的扩展类型,本教程未涉及。app

#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结构。ui

第一点是对象的定义:线程

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,这个值将自动继承基类型的。

相关文章
相关标签/搜索