当前位置: 首页 > article >正文

使用 C 或 C++ 开发 Python库(02)

一、说明

如果您知道如何使用 C 编程,那么向 Python 中添加新的内置模块非常容易。此类扩展模块可以完成两件无法在 Python 中直接完成的事情:它们可以实现新的内置对象类型,并且它们可以调用 C 库函数和系统调用。

为了支持扩展,Python API(应用程序编程接口)定义了一组函数、宏和变量,用于访问 Python 运行时系统的大多数方面。通过包含标头,Python API 被整合到 C 源文件中"Python.h"。

二、定义扩展类型

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

2.1.基础知识

CPython运行时将所有 Python 对象视为 PyObject *类型的变量,该类型变量是所有 Python 对象的“基类型”。结构本身仅包含对象的 引用计数和指向对象“类型对象”的指针。这就是操作所在;类型对象决定解释器调用哪些 © 函数,例如,在对象上查找属性、调用方法或将其乘以另一个对象。这些 C 函数称为“类型方法”。PyObject

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

这种事情只能通过例子来解释,所以这里有一个最小但完整的模块,它定义了一个Custom在 C 扩展模块内命名的新类型custom:

笔记 我们在这里展示的是定义静态扩展类型的传统方法 。它应该足以满足大多数用途。C API 还允许使用 PyType_FromSpec()函数定义堆分配的扩展类型,本教程未介绍这一点。

#define PY_SSIZE_T_CLEAN
#include <Python.h>

typedef struct {
    PyObject_HEAD
    /* Type-specific fields go here. */
} CustomObject;

static PyTypeObject CustomType = {
    .ob_base = PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "custom.Custom",
    .tp_doc = PyDoc_STR("Custom objects"),
    .tp_basicsize = sizeof(CustomObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT,
    .tp_new = PyType_GenericNew,
};

static PyModuleDef custommodule = {
    .m_base = 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;

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

    return m;
}

现在需要一次性理解很多内容,但希望这些内容与上一章的内容相似。此文件定义了三件事:

  • Custom 对象包含的内容:这是CustomObject 结构,为每个实例分配一次Custom。

  • Custom 类型的行为方式:这是CustomType结构,它定义了一组标志和函数指针,解释器在请求特定操作时会检查这些标志和函数指针。

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

第一位是:

typedef struct {
    PyObject_HEAD
} CustomObject;

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

笔记 上面的宏后面没有分号PyObject_HEAD。小心不要意外添加分号:有些编译器会报错。
当然,对象通常除了存储标准 PyObject_HEAD样板数据之外还存储其他数据;例如,这里是标准 Python 浮点数的定义:

typedef struct {
    PyObject_HEAD
    double ob_fval;
} PyFloatObject;
第二位是类型对象的定义。

static PyTypeObject CustomType = {
    .ob_base = PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "custom.Custom",
    .tp_doc = PyDoc_STR("Custom objects"),
    .tp_basicsize = sizeof(CustomObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT,
    .tp_new = PyType_GenericNew,
};

笔记 我们建议使用如上所述的 C99 风格的指定初始化器,以避免列出所有PyTypeObject您不关心的字段,也避免关心字段的声明顺序。
PyTypeObjectin的实际定义比上面的定义object.h有更多字段。其余字段将由 C 编译器用零填充,除非您需要它们,否则通常不会明确指定它们。

我们将逐个分析,每次一个字段:

.ob_base = 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兼容非常重要。pydocpickle

.tp_basicsize = sizeof(CustomObject),
.tp_itemsize = 0,

这样,Python 就知道在创建新Custom实例时要分配多少内存。 tp_itemsize仅用于可变大小的对象,否则应该为零。

笔记 tp_basicsize如果您希望您的类型可从 Python 中子类化,并且您的类型与其基类型相同 ,则可能会遇到多重继承问题。您的类型的 Python 子类必须首先在其 中列出您的类型__bases__,否则它将无法调用您的类型的 new()方法而不会出错。您可以通过确保您的类型具有比tp_basicsize其基类型更大的 值来避免此问题。大多数情况下,这都是正确的,因为要么您的基类型是object,要么您将向基类型添加数据成员,从而增加其大小。
我们将类标志设置为Py_TPFLAGS_DEFAULT。

.tp_flags = Py_TPFLAGS_DEFAULT,

所有类型都应在其标志中包含此常量。它启用至少在 Python 3.3 之前定义的所有成员。如果您需要更多成员,则需要对相应的标志进行 OR 操作。

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

.tp_doc = PyDoc_STR("Custom objects"),

为了能够创建对象,我们必须提供一个tp_new 处理程序。这相当于 Python 方法__new__(),但必须明确指定。在这种情况下,我们可以使用 API 函数提供的默认实现PyType_GenericNew()。

.tp_new = PyType_GenericNew,
文件中的所有其他内容都应该很熟悉,除了以下一些代码 PyInit_custom():

if (PyType_Ready(&CustomType) < 0)
    return;

这将初始化Custom类型,并将多个成员填充为适当的默认值,包括ob_type我们最初设置为的NULL。

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

这会将类型添加到模块字典中。这使我们能够 Custom通过调用Custom类来创建实例:

import custom
mycustom = custom.Custom()

就这样!剩下的就是构建它;将上述代码放入名为的文件中 custom.c,

[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"

[project]
name = "custom"
version = "1"

在名为的文件中pyproject.toml,并且

from setuptools import Extension, setup
setup(ext_modules=[Extension("custom", ["custom.c"])])

在名为 ; 的文件中setup.py,然后输入

python -m pip install .

在 shell 中应该custom.so在子目录中生成一个文件并安装它;现在启动 Python — — 您应该能够 操作对象。import customCustom

那不太难,不是吗?

当然,当前的 Custom 类型相当无趣。它没有数据,也不执行任何操作。它甚至不能被子类化。

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

让我们扩展基本示例以添加一些数据和方法。我们还使该类型可用作基类。我们将创建一个新模块,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接受参数,但 函数指针需要接收参数。否则,编译器会发出警告。这是 C 中的面向对象多态性!CustomObject *tp_deallocPyObject *
我们要确保名字和姓氏初始化为空字符串,因此我们提供了一个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 初始化为非 默认值。firstlastNULL

tp_new传递被实例化的类型(CustomType如果实例化了子类,则不一定)和调用该类型时传递的任何参数,并期望返回创建的实例。 tp_new处理程序始终接受位置参数和关键字参数,但它们通常会忽略参数,而将参数处理留给初始化程序(又名tp_init C 或__init__Python)方法。

笔记 tp_new不应该tp_init明确调用,因为解释器会自己执行此操作。
实现tp_new中调用tp_alloc slot来分配内存:

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,
每个成员定义都有成员名称、类型、偏移量、访问标志和文档字符串。有关详细信息,请参阅下面的通用属性管理部分。

这种方法的缺点是它没有提供方法来限制可以分配给 Python 属性的对象类型。我们期望名字和姓氏是字符串,但可以分配任何 Python 对象。此外,可以删除属性,将 C 指针设置为NULL。即使我们可以确保成员初始化为非值,但如果删除属性NULL,成员也可以设置为。NULL

我们定义一个方法,Custom.name()将对象名称作为名字和姓氏的连接输出。

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

该方法实现为一个 C 函数,以Custom(或 Custom子类) 实例作为第一个参数。方法始终以实例作为第一个参数。方法通常也接受位置参数和关键字参数,但在这种情况下我们不接受任何参数,也不需要接受位置参数元组或关键字参数字典。此方法相当于 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 */
};

(请注意,我们使用标志来表明该方法除了selfMETH_NOARGS之外不需要其他参数)

并将其分配给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 .

2.3.提供对数据属性更精细的控制

在本节中,我们将对示例中设置first和 属性的方式提供更精细的控制。在我们模块的先前版本中,实例变量和 可以设置为非字符串值,甚至可以删除。我们希望确保这些属性始终包含字符串。lastCustomfirstlast

#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, "|UUi", kwlist,
                                     &first, &last,
                                     &self->number))
        return -1;

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

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

static PyObject *
Custom_getfirst(CustomObject *self, void *closure)
{
    return Py_NewRef(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_SETREF(self->first, Py_NewRef(value));
    return 0;
}

static PyObject *
Custom_getlast(CustomObject *self, void *closure)
{
    return Py_NewRef(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_SETREF(self->last, Py_NewRef(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 = {
    .ob_base = PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "custom3.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,
    .tp_getset = Custom_getsetters,
};

static PyModuleDef custommodule = {
    .m_base = 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;

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

    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对象和一个“闭包”,后者是一个 void 指针。在这种情况下,闭包将被忽略。(闭包支持一种高级用法,即将定义数据传递给 getter 和 setter。例如,这可用于允许一组 getter 和 setter 函数根据闭包中的数据决定要获取或设置的属性。)

向 setter 函数传递Custom对象、新值和闭包。新值可能是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是上面提到的“闭包”。在本例中,我们没有使用闭包,因此我们直接传递NULL。

我们还删除了这些属性的成员定义:

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

我们还需要更新tp_init处理程序以仅允许传递字符串[ 3 ] :

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。

我们还像以前一样在初始化函数中重命名模块初始化函数和模块名称,并在 setup.py文件中添加额外的定义。

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

l = []
l.append(l)
del l
在这个例子中,我们创建了一个包含自身的列表。当我们删除它时,它仍然有来自自身的引用。它的引用计数不会降为零。幸运的是,Python 的循环垃圾收集器最终会发现该列表是垃圾并将其释放。

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

import custom3 class Derived(custom3.Custom): pass

n = Derived() n.some_attribute = n 为了允许Custom参与引用循环的实例被循环 GC
正确检测和收集,我们的Custom类型需要填充两个额外的槽并启用启用这些槽的标志:

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

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

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

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

static PyObject * Custom_getfirst(CustomObject *self, void *closure) {
return Py_NewRef(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_XSETREF(self->first, Py_NewRef(value));
return 0; }

static PyObject * Custom_getlast(CustomObject *self, void *closure) {
return Py_NewRef(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_XSETREF(self->last, Py_NewRef(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 = {
.ob_base = PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = “custom4.Custom”,
.tp_doc = PyDoc_STR(“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 = {
.m_base = 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;

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

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 提供了一个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和argPy_VISIT()。
其次,我们需要提供一种方法来清除任何可以参与循环的子对象:

static int
Custom_clear(CustomObject *self)
{
    Py_CLEAR(self->first);
    Py_CLEAR(self->last);
    return 0;
}

注意宏的使用Py_CLEAR()。这是清除任意类型的数据属性并减少其引用计数的推荐且安全的方法。如果您Py_XDECREF()在将属性设置为之前调用该宏NULL,则属性的析构函数可能会回调到再次读取该属性的代码中(尤其是如果存在引用循环)。

笔记 你可以Py_CLEAR()通过以下方式模拟:

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

尽管如此,删除属性时始终使用这种方法会更加容易且不容易出错Py_CLEAR()。不要以牺牲稳健性为代价进行微优化!

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

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,
差不多就是这样。如果我们编写了自定义tp_alloc或 tp_free处理程序,则需要修改它们以进行循环垃圾收集。大多数扩展将使用自动提供的版本。

2.5.子类化其他类型
可以创建从现有类型派生的新扩展类型。最简单的方法是从内置类型继承,因为扩展可以轻松使用它所需的类型。在扩展模块之间PyTypeObject共享这些结构可能很困难。PyTypeObject

在此示例中,我们将创建一个SubList从内置list类型继承的类型。新类型将与常规列表完全兼容,但将具有increment()增加内部计数器的附加方法:

import sublist
s = sublist.SubList(range(3))
s.extend(s)
print(len(s))
6
print(s.increment())
1
print(s.increment())
2
#define PY_SSIZE_T_CLEAN
#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 = PyDoc_STR("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;

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

    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;

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

    return m;
}

在调用之前PyType_Ready(),类型结构必须 tp_base填充插槽。当我们派生现有类型时,没有必要用填充插槽tp_alloc ——PyType_GenericNew()来自基类型的分配函数将被继承。

此后,调用PyType_Ready()和添加类型对象到模块与基本示例相同Custom。


http://www.kler.cn/news/360750.html

相关文章:

  • 车辆管理系统设计与SpringBoot技术融合
  • 微前端架构的思考 :专注于多框架的并存可能并不是唯一的方向 — 探讨新前端的分层式微前端架构
  • NPOI 基础操作,创建一个 docx 并打开
  • SQL Server动态列转行
  • 如何提升游戏的用户留存率
  • linux上sed的常用操作
  • [数据采集技术:实践02]:requests,lxml,BeautifulSoup模块的使用
  • vue3--实现瀑布流-长列表-懒加载
  • 电脑视频剪辑大比拼,谁更胜一筹?
  • SaaS架构:中央库存系统架构设计
  • 蘑菇分类识别数据集(猫脸码客 第222期)
  • C++源码生成·序章
  • 【网络原理】TCP/IP五层网络模型之网络层-----IP协议详解,建议收藏!!
  • phpstudy如何搭建靶场的教程--适合入门小白
  • Python Numpy 实现神经网络自动训练:反向传播与激活函数的应用详解
  • sharding sphere 加解密功能 like语句 SQL 解析报错
  • OBOO鸥柏:液晶拼接大屏搭载节点盒分布式集中管控控制系统新技术
  • 2024年软件设计师中级(软考中级)详细笔记【7】面向对象技术(上)(分值10+)
  • Java项目-基于springboot框架的网上书城系统项目实战(附源码+文档)
  • jQuery:元素控制 事件