(二)定義擴展類型(翻譯)

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,這個值將自動繼承基類型的。

相關文章
相關標籤/搜索