(二)定義擴展類型(翻譯)
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.h
中PyTypeObject
的實際定義包含更多字段,其余字段將由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_new
和tp_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
,這個值將自動繼承基類型的。
(二)定義擴展類型(翻譯)