LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
查看: 285|回复: 0

向基本示例添加数据和方法

[复制链接]
发表于 2024-1-17 17:18:43 | 显示全部楼层 |阅读模式

向基本示例添加数据和方法
让我们通过添加一些数据和方法来扩展这个基本示例。 让我们再使该类型可以作为基类使用。 我们将创建一个新模块 custom2 来添加这些功能:

#define PY_SSIZE_T_CLEAN
#include <;Python.h>
#include <stddef.h> /* for offsetof() */

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;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,
                                     &first, &last,
                                     &self->number))
        return -1;

    if (first) {
        Py_XSETREF(self->first, Py_NewRef(first));
    }
    if (last) {
        Py_XSETREF(self->last, Py_NewRef(last));
    }
    return 0;
}

static PyMemberDef Custom_members[] = {
    {"first", Py_T_OBJECT_EX, offsetof(CustomObject, first), 0,
     "first name"},
    {"last", Py_T_OBJECT_EX, offsetof(CustomObject, last), 0,
     "last name"},
    {"number", Py_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 = {
    .ob_base = PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "custom2.Custom",
    .tp_doc = PyDoc_STR("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 = {
    .m_base =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;

    if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
        Py_DECREF(m);
        return NULL;
    }

    return m;
}
该模块的新版本包含多处修改。

现在 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 中途失败时发生)。 随后它将调用对象类型的 tp_free 成员(通过 Py_TYPE(self) 计算得到)来释放对象的内存。 请注意对象类型可以不是 CustomType,因为对象可能是一个子类的实例。

备注 上面需要强制转换 destructor 是因为我们定义了 Custom_dealloc 接受一个 CustomObject * 参数,但 tp_dealloc 函数指针预期接受一个 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(),就像上面 Custom 类型的第一个版本所做的那样。 在此情况下,我们使用 tp_new 处理句柄来将 first 和 last 属性初始化为非 NULL 的默认值。

tp_new 将接受被实例化的类型(不要求为 CustomType,如果被实例化的是一个子类)以及在该类型被调用时传入的任何参数,并预期返回所创建的实例。 tp_new 处理句柄总是接受位置和关键字参数,但它们总是会忽略这些参数,而将参数处理留给初始化(即 C 中的 tp_init 或 Python 中的 __init__ 函数)方法来执行。

备注 tp_new 不应显式地调用 tp_init,因为解释器会自行调用它。
tp_new 实现会调用 tp_alloc 槽位来分配内存:

self = (CustomObject *) type->tp_alloc(type, 0);
由于内存分配可能会失败,我们必须在继续执行之前检查 tp_alloc 结果确认其不为 NULL。

备注 我们没有自行填充 tp_alloc 槽位。 而是由 PyType_Ready() 通过从我们的基类继承来替我们填充它,其中默认为 object。 大部分类型都是使用默认的分配策略。
备注 如果您要创建一个协作式 tp_new (它会调用基类型的 tp_new 或 __new__()),那么你 不能 在运行时尝试使用方法解析顺序来确定要调用的方法。 必须总是静态地确定你要调用的类型,并直接调用它的 tp_new,或是通过 type->tp_base->tp_new。 如果你不这样做,你的类型的同样继承自其它由 Python 定义的类的 Python 子类可能无法正常工作。 (具体地说,你可能无法创建这样的子类的实例而是会引发 TypeError。)
我们还定义了一个接受参数来为我们的实例提供初始值的初始化函数:

static int
Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"first", "last", "number", NULL};
    PyObject *first = NULL, *last = NULL, *tmp;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,
                                     &first, &last,
                                     &self->number))
        return -1;

    if (first) {
        tmp = self->first;
        Py_INCREF(first);
        self->first = first;
        Py_XDECREF(tmp);
    }
    if (last) {
        tmp = self->last;
        Py_INCREF(last);
        self->last = last;
        Py_XDECREF(tmp);
    }
    return 0;
}
通过填充 tp_init 槽位。

.tp_init = (initproc) Custom_init,
tp_init 槽位在 Python 中暴露为 __init__() 方法。 它被用来在创建对象后对其进行初始化。 初始化器总是接受位置和关键字参数,它们应当在成功时返回 0 而在出错时返回 -1。

不同于 tp_new 处理句柄,tp_init 不保证一定会被调用 (例如,在默认情况下 pickle 模块不会在未解封的实例上调用 __init__())。 它还可能被多次调用。 任何人都可以在我们的对象上调用 __init__() 方法。 因此,我们在为属性赋新值时必须格外小心。 例如像这样给 first 成员赋值:

if (first) {
    Py_XDECREF(self->first);
    Py_INCREF(first);
    self->first = first;
}
但是这可能会有危险。 我们的类型没有限制 first 成员的类型,因此它可以是任何种类的对象。 它可以带有一个会执行尝试访问 first 成员的代码的析构器;或者该析构器可能会释放 全局解释器锁 并让任意代码在其他线程中运行来访问和修改我们的对象。

为了保持谨慎并使我们避免这种可能性,我们几乎总是要在减少成员的引用计数之前给它们重新赋值。 什么时候我们可以不必再这样做?

当我们明确知道引用计数大于 1 的时候;

当我们知道对象的释放 1 既不会释放 GIL 也不会导致任何对我们的类型的代码的回调的时候;

当减少一个 tp_dealloc 处理句柄内不支持循环垃圾回收的类型的引用计数的时候 2.

我们可能会想将我们的实例变量暴露为属性。 有几种方式可以做到这一点。 最简单的方式是定义成员的定义:

static PyMemberDef Custom_members[] = {
    {"first", Py_T_OBJECT_EX, offsetof(CustomObject, first), 0,
     "first name"},
    {"last", Py_T_OBJECT_EX, offsetof(CustomObject, last), 0,
     "last name"},
    {"number", Py_T_INT, offsetof(CustomObject, number), 0,
     "custom number"},
    {NULL}  /* Sentinel */
};
并将定义放置到 tp_members 槽位中:

.tp_members = Custom_members,
每个成员的定义都有成员名称、类型、偏移量、访问旗标和文档字符串。 请参阅下面的 泛型属性管理 小节来了解详情。section below for details.

此方式的缺点之一是它没有提供限制可被赋值给 Python 属性的对象类型的办法。 我们预期 first 和 last 的名称为字符串,但它们可以被赋值为任意 Python 对象。 此外,这些属性还可以被删除,并将 C 指针设为 NULL。 即使我们可以保证这些成员被初始化为非 NULL 值,如果这些属性被删除这些成员仍可被设为 NULL。

我们定义了一个单独的方法,Custom.name(),它将对象名称输出为 first 和 last 的拼接。

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);
}
该方法以的实现形式是一个接受 Custom (或:class:!Custom 的子类) 实例作为第一个参数的 C 函数。 方法总是接受一个实例作为第一个参数。 方法往往也接受位置和关键字参数,但在本例中我们未接受任何参数也不需要接受位置参数元组或关键字参数字典。 该方法等价于以下 Python 方法:

def name(self):
    return "%s %s" % (self.first, self.last)
请注意我们必须检查 first 和 last 成员是否可能为 NULL。 这是因为它们可以被删除,在此情况下它们会被设为 NULL。 更好的做法是防止删除这些属性并将属性的值限制为字符串。 我们将在下一节了解如何做到这一点。

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

static PyMethodDef Custom_methods[] = {
    {"name", (PyCFunction) Custom_name, METH_NOARGS,
     "Return the name, combining the first and last name"
    },
    {NULL}  /* Sentinel */
};
(请注意我们使用了 METH_NOARGS 旗标来指明该方法不准备接受除 self 以外的任何参数)

并将其赋给 tp_methods 槽位:

.tp_methods = Custom_methods,
最后,我们将使我们的类型可被用作派生子类的基类。 我们精心地编写我们的方法以便它们不会随意假定被创建或使用的对象类型,所以我们需要做的就是将 Py_TPFLAGS_BASETYPE 添加到我们的类旗标定义中:

.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
我们将 PyInit_custom() 重命名为 PyInit_custom2(),更新 PyModuleDef 结构体中的模块名称,并更新 PyTypeObject 结构体中的完整类名。

最后,我们更新 setup.py 文件来包括新的模块,

from setuptools import Extension, setup
setup(ext_modules=[
    Extension("custom", ["custom.c"]),
    Extension("custom2", ["custom2.c"]),
])
然后我们重新安装以便能够 import custom2:

python -m pip install .
您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表