3.定义扩展类型:各种主题

本节旨在快速介绍您可以实施的各种类型的方法以及它们的用途.

这是PyTypeObject,有些字段只使用了indebug构建省略:

typedef struct _typeobject {
    PyObject_VAR_HEAD
    const char *tp_name; /* For printing, in format "<module>.<name>" */
    Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */

    /* Methods to implement standard operations */

    destructor tp_dealloc;
    printfunc tp_print;
    getattrfunc tp_getattr;
    setattrfunc tp_setattr;
    PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2)
                                    or tp_reserved (Python 3) */
    reprfunc tp_repr;

    /* Method suites for standard classes */

    PyNumberMethods *tp_as_number;
    PySequenceMethods *tp_as_sequence;
    PyMappingMethods *tp_as_mapping;

    /* More standard operations (here for binary compatibility) */

    hashfunc tp_hash;
    ternaryfunc tp_call;
    reprfunc tp_str;
    getattrofunc tp_getattro;
    setattrofunc tp_setattro;

    /* Functions to access object as input/output buffer */
    PyBufferProcs *tp_as_buffer;

    /* Flags to define presence of optional/expanded features */
    unsigned long tp_flags;

    const char *tp_doc; /* Documentation string */

    /* call function for all accessible objects */
    traverseproc tp_traverse;

    /* delete references to contained objects */
    inquiry tp_clear;

    /* rich comparisons */
    richcmpfunc tp_richcompare;

    /* weak reference enabler */
    Py_ssize_t tp_weaklistoffset;

    /* Iterators */
    getiterfunc tp_iter;
    iternextfunc tp_iternext;

    /* Attribute descriptor and subclassing stuff */
    struct PyMethodDef *tp_methods;
    struct PyMemberDef *tp_members;
    struct PyGetSetDef *tp_getset;
    struct _typeobject *tp_base;
    PyObject *tp_dict;
    descrgetfunc tp_descr_get;
    descrsetfunc tp_descr_set;
    Py_ssize_t tp_dictoffset;
    initproc tp_init;
    allocfunc tp_alloc;
    newfunc tp_new;
    freefunc tp_free; /* Low-level free-memory routine */
    inquiry tp_is_gc; /* For PyObject_IS_GC */
    PyObject *tp_bases;
    PyObject *tp_mro; /* method resolution order */
    PyObject *tp_cache;
    PyObject *tp_subclasses;
    PyObject *tp_weaklist;
    destructor tp_del;

    /* Type attribute cache version tag. Added in version 2.6 */
    unsigned int tp_version_tag;

    destructor tp_finalize;

} PyTypeObject;

现在这是很多方法不过不用太担心 – 如果你有一个你想要定义的类型,你很可能只会实现其中的一些类型.

正如您现在所期望的那样,我们将对此进行讨论并提供有关各种处理程序的更多信息。我们不会按照他们在结构中定义的顺序,因为有许多历史包袱会影响字段的排序。通常最容易找到一个包含所需字段的示例,然后更改值以适合您的新类型.

const char *tp_name; /* For printing */

该类型的名称 – 如前一章所述,这将显示出不同的地方,几乎完全用于诊断目的。尝试选择在这种情况下有用的东西!

Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */

这些字段告诉运行时创建此类型的新对象时要分配多少内存。Python有一些内置的变量长度结构支持(想想:字符串,元组)这就是tp_itemsize这将在稍后处理.

const char *tp_doc;

当你的Python脚本引用obj.__doc__来检索doc字符串时,你可以放一个你想要返回的字符串(或它的地址).

现在我们来看基本类型方法 – 大多数扩展类型将实现的方法.

3.1。调用完成和取消分配

destructor tp_dealloc;

当您的类型实例的引用计数减少为零并且Python解释器想要回收它时,将调用此函数。如果你的类型有内存可供免费或其他清理执行,你可以把它放在这里。该对象本身也需要在这里释放。这是一个这个功能的例子:

static void
newdatatype_dealloc(newdatatypeobject *obj)
{
    free(obj->obj_UnderlyingDatatypePtr);
    Py_TYPE(obj)->tp_free(obj);
}

解除分配器功能的一个重要要求是它只留下任何未决的例外。这很重要,因为解析器通常被称为解释器展开Python堆栈;当堆栈退出异常(而不是正常返回)时,没有做任何事情来保护分配器不会看到已经设置了异常。解除分配器执行的任何可能导致其他Python代码被执行的操作都可能检测到已设置异常。这可能导致翻译误导。防止这种情况的正确方法是在执行不安全操作之前保存挂起的异常,并将其恢复为whendone。这可以使用PyErr_Fetch()PyErr_Restore()函数来完成

static void
my_dealloc(PyObject *obj)
{
    MyObject *self = (MyObject *) obj;
    PyObject *cbresult;

    if (self->my_callback != NULL) {
        PyObject *err_type, *err_value, *err_traceback;

        /* This saves the current exception state */
        PyErr_Fetch(&err_type, &err_value, &err_traceback);

        cbresult = PyObject_CallObject(self->my_callback, NULL);
        if (cbresult == NULL)
            PyErr_WriteUnraisable(self->my_callback);
        else
            Py_DECREF(cbresult);

        /* This restores the saved exception state */
        PyErr_Restore(err_type, err_value, err_traceback);

        Py_DECREF(self->my_callback);
    }
    Py_TYPE(obj)->tp_free((PyObject*)self);
}

注意

你可以在deallocator函数中安全地做什么限制。首先,如果你的类型支持垃圾收集(使用tp_traverse和/或tp_clear),那么一些对象的成员可以在时间之前被清除或最终确定tp_dealloc。二,在tp_dealloc,您的对象处于不稳定状态:其referencecount等于零。任何对非平凡对象或API的调用(如上例所示)都可能最终调用tp_dealloc再次,造成双重自由和崩溃.

从Python 3.4开始,建议不要在tp_dealloc中放入任何复杂的最终化代码,而是使用新的tp_finalize类型方法。

也可以看看

PEP 442 解释了新的敲定方案.

 

3.2。对象演示

在Python中,有两种方法可以生成对象的文本表示:repr()函数和str()功能。(print()功能只调用str()。)这些处理程序都是可选的.

reprfunc tp_repr;
reprfunc tp_str;

tp_reprhandler应返回一个字符串对象,该对象包含要为其调用的实例的表示。这是一个简单的例子:

static PyObject *
newdatatype_repr(newdatatypeobject * obj)
{
    return PyUnicode_FromFormat("Repr-ified_newdatatype{{size:%d}}",
                                obj->obj_UnderlyingDatatypePtr->size);
}

如果没有tp_repr如果指定了handler,解释器将提供使用类型的tp_name和对象的唯一标识值的表示.

tp_str处理程序是str()什么tp_repr上面描述的处理程序是repr();也就是说,当Python代码在对象的实例上调用str()时调用它。它的实现非常类似于tp_repr函数,但结果字符串用于人类消费。如果tp_str没有说明,tp_repr改为使用处理程序.

这是一个简单的例子:

static PyObject *
newdatatype_str(newdatatypeobject * obj)
{
    return PyUnicode_FromFormat("Stringified_newdatatype{{size:%d}}",
                                obj->obj_UnderlyingDatatypePtr->size);
}

3.3。属性管理

对于每个可以支持属性的对象,相应的类型必须提供控制属性解析方式的函数。需要一个能够检索属性(如果有任何定义)的函数,以及另一个设置属性(如果允许设置属性)。删除属性是一种特殊情况,传递给处理程序的新值是NULL.

Python支持两对属性处理程序;支持attributesonly的类型需要实现一对的函数。不同之处在于,onepair将属性的名称作为char*,而其他对象则为PyObject*。每种类型都可以使用任何一对为实现的方便而更多的使用.

getattrfunc  tp_getattr;        /* char * version */
setattrfunc  tp_setattr;
/* ... */
getattrofunc tp_getattro;       /* PyObject * version */
setattrofunc tp_setattro;

如果访问对象的属性总是一个简单的操作(这将很快解释),有一些通用的实现可以用来提供PyObject*属性管理函数的版本。从Python 2.2开始几乎完全消除了对特定于类型的属性处理程序的实际需求,尽管有许多示例尚未更新以使用一些可用的新通用机制.

 

3.3.1。通用属性管理

大多数扩展类型仅使用simple属性。那么,什么使得属性变得简单?只有几个必须满足的条件:

  1. PyType_Ready()被称为时,必须知道属性的名称.
  2. 不需要特殊处理来记录属性被查找orset,也不需要根据值进行操作。

请注意,此列表不会对属性的值进行任何限制,计算值时,或者相关数据的存储方式.

当调用PyType_Ready()时,它使用了type对象引用的三个表来创建描述符 s,它们被放置在type对象的字典中。每个描述符控制对instanceobject的一个属性的访问。每个表都是可选的;如果这三个都是NULL,那么类型的实例只会有从它们的基类继承的属性,并且应该留下tp_getattrotp_setattro字段NULL,允许基类型处理属性.

表被声明为对象类型的三个字段:

struct PyMethodDef *tp_methods;
struct PyMemberDef *tp_members;
struct PyGetSetDef *tp_getset;

如果tp_methods不是NULL,它必须参考PyMethodDef结构。表中的每个条目都是thisstructure的一个实例:

typedef struct PyMethodDef {
    const char  *ml_name;       /* method name */
    PyCFunction  ml_meth;       /* implementation function */
    int          ml_flags;      /* flags */
    const char  *ml_doc;        /* docstring */
} PyMethodDef;

应该为该类型提供的每个方法定义一个条目;从基类型继承的方法不需要任何条目。最后还需要一个额外的条目;它是一个哨兵,标志着阵列的结束。//哨所的ml_name字段必须是NULL.

第二个表用于定义直接映射到实例中存储的数据的属性。支持各种原始C类型,访问可能是只读或读写。表中的结构定义为:

typedef struct PyMemberDef {
    const char *name;
    int         type;
    int         offset;
    int         flags;
    const char *doc;
} PyMemberDef;

对于表中的每个条目,将构造一个描述符并将其添加到将能够提取的类型中实例结构中的值。type字段应包含structmember.h标题中定义的类型代码之一;该值将用于确定如何将Python值转换为C值和从C值转换。flags字段用于控制如何访问属性的tostore标志.

structmember.h;它们可以用bitwise-OR.

不变 含义
READONLY 永不可写
READ_RESTRICTED 在限制模式下无法读取.
WRITE_RESTRICTED 在限制模式下不可写.
RESTRICTED 在限制模式下不可读写.

使用tp_members在运行时使用的表构建描述符是这样定义的任何属性都可以通过提供表中的文本来具有关联的文档字符串。应用程序可以使用内省API从类对象中检索描述符,并使用其__doc__属性。

tp_methods桌子,一个带有name的价值 NULL是必须的。

3.3.2。特定类型的属性管理

为简单起见,只有char*版本将在这里展示;name参数的类型是char*PyObject*界面的味道。此示例有效地执行与上面的通用示例相同的操作,但不使用Python 2.2中添加的genericsupport。它解释了如何调用处理程序函数,因此如果你确实需要扩展它们的功能,你就会明白需要做什么.

tp_getattr当对象需要attributelook-up时调用handler。在__getattr__()一个班级的方法会叫.

这是一个例子:

static PyObject *
newdatatype_getattr(newdatatypeobject *obj, char *name)
{
    if (strcmp(name, "data") == 0)
    {
        return PyLong_FromLong(obj->data);
    }

    PyErr_Format(PyExc_AttributeError,
                 "'%.50s' object has no attribute '%.400s'",
                 tp->tp_name, name);
    return NULL;
}

tp_setattr当调用类实例的__setattr__()__delattr__()方法时调用handler。当删除anattribute时,第三个参数将是NULL。这是一个简单引发异常的示例;如果这真的是你想要的全部,那么tp_setattr处理程序应设置为NULL.

static int
newdatatype_setattr(newdatatypeobject *obj, char *name, PyObject *v)
{
    PyErr_Format(PyExc_RuntimeError, "Read-only attribute: %s", name);
    return -1;
}

3.4。对象比较

richcmpfunc tp_richcompare;

tp_richcompare在需要比较时调用handler。丰富的比较方法,如__lt__(),也是PyObject_RichCompare()PyObject_RichCompareBool().

使用两个Python对象和运算符作为参数调用此函数,其中运算符是Py_EQ, Py_NE, Py_LE, Py_GT,Py_LTPy_GT。它应该比较指定的操作符比较两个对象并返回Py_TruePy_False如果比较成功,Py_NotImplemented表示比较未实现,另一个对象的比较方法应该尝试,或NULL如果设置了异常.

下面是一个示例实现,如果内部指针的大小相等,则认为数据类型相等:

static PyObject *
newdatatype_richcmp(PyObject *obj1, PyObject *obj2, int op)
{
    PyObject *result;
    int c, size1, size2;

    /* code to make sure that both arguments are of type
       newdatatype omitted */

    size1 = obj1->obj_UnderlyingDatatypePtr->size;
    size2 = obj2->obj_UnderlyingDatatypePtr->size;

    switch (op) {
    case Py_LT: c = size1 <  size2; break;
    case Py_LE: c = size1 <= size2; break;
    case Py_EQ: c = size1 == size2; break;
    case Py_NE: c = size1 != size2; break;
    case Py_GT: c = size1 >  size2; break;
    case Py_GE: c = size1 >= size2; break;
    }
    result = c ? Py_True : Py_False;
    Py_INCREF(result);
    return result;
 }

3.5。抽象协议支持

Python支持各种抽象‘协议;’提供使用这些接口的具体接口记录在抽象对象层.

许多这些抽象接口是在Python实现的早期开发中定义的。特别是,数字,映射和序列协议从一开始就是Python的一部分。随着时间的推移,已经添加了其他协议。对于依赖于类型实现中的若干处理程序例程的协议,旧协议已被定义为类型对象引用的处理程序的可选块。对于较新的协议,主类型对象中存在附加的时隙,其中设置标志位以指示时隙存在并且应由解释器检查。(flagbit并不表示插槽值不是空值。可以设置标志以指示存在插槽,但是仍然可以填充插槽。)

PyNumberMethods   *tp_as_number;
PySequenceMethods *tp_as_sequence;
PyMappingMethods  *tp_as_mapping;

如果您希望您的对象能够像数字,序列或消息对象那样行动,那么您可以放置​​实现Ctype PyNumberMethods, PySequenceMethodsPyMappingMethods, 分别。您应该使用适当的值填充此结构。您可以在Python源代码发布的Objects目录中找到各自使用的示例.

hashfunc tp_hash;

如果您选择提供此函数,则应返回数据类型实例的哈希值。这是一个简单的例子:

static Py_hash_t
newdatatype_hash(newdatatypeobject *obj)
{
    Py_hash_t result;
    result = obj->some_size + 32767 * obj->some_number;
    if (result == -1)
       result = -2;
    return result;
}

Py_hash_t是一个带有平台变化宽度的有符号整数类型。返回-1来自tp_hash表示错误,这就是为什么你应该小心避免在哈希计算成功时返回它,如上所示.

ternaryfunc tp_call;

当“数据”类型的实例被“调用”时调用此函数,例如,如果obj1是数据类型的实例,并且Python脚本包含obj1("hello")tp_call处理程序被调用.

这个函数有三个参数:

  1. self是数据类型的实例,它是调用的主题。如果调用是obj1("hello"),则selfobj1.
  2. args是一个包含调用参数的元组。您可以使用PyArg_ParseTuple()来提取参数.
  3. kwds是传递的关键字参数的字典。如果这是非 – NULL并且您支持关键字参数,请使用PyArg_ParseTupleAndKeywords()来提取参数。如果你不想支持关键字参数而且这不是NULL,请提示TypeError并附上一条消息,说明不支持关键字参数.

这是一个玩具tp_call implementation:

static PyObject *
newdatatype_call(newdatatypeobject *self, PyObject *args, PyObject *kwds)
{
    PyObject *result;
    const char *arg1;
    const char *arg2;
    const char *arg3;

    if (!PyArg_ParseTuple(args, "sss:call", &arg1, &arg2, &arg3)) {
        return NULL;
    }
    result = PyUnicode_FromFormat(
        "Returning -- value: [%d] arg1: [%s] arg2: [%s] arg3: [%s]\n",
        obj->obj_UnderlyingDatatypePtr->size,
        arg1, arg2, arg3);
    return result;
}
/* Iterators */
getiterfunc tp_iter;
iternextfunc tp_iternext;

这些函数提供对迭代器协议的支持。两个处理程序只接受一个参数,即调用它们的实例,并返回一个新引用。在出错的情况下,他们应该设置一个异常并返回NULL. tp_iter对应于Python __iter__()方法,而tp_iternext对应于Python __next__()方法.

任何 iterable 对象必须实现tp_iter处理程序,它必须返回迭代器对象。这里适用的指南适用于Python类:

  • 对于可以支持多个独立迭代器的集合(例如列表和元组),应该通过调用tp_iter.
  • 对象来创建和返回一个新的迭代器,这些对象只能迭代一次(通常是由于副作用,例如文件对象)可以通过返回一个新的引用来实现tp_iter – 因此也应该实现tp_iternext handler.

Yny iterator 对象应该实现tp_itertp_iternext。迭代器的tp_iter处理程序应该向迭代器返回一个新的引用。它的tp_iternext处理程序应该返回迭代中下一个对象的新引用,如果有的话。如果迭代已经到了结尾,tp_iternext可能会返回NULL而不设置异常,或者可以设置StopIteration in addition返回NULL;避免异常可以产生略微更好的性能。如果发生实际错误,tp_iternext应该总是设置异常并返回NULL.

 

3.6。弱参考支持

Python的弱引用实现的目标之一是允许任何类型参与弱引用机制而不会产生性能关键对象(例如数字)的开销.

参见

文档weakref module.

对于一个弱引用的对象,扩展类型必须做两件事:

  1. 在C对象中包含一个PyObject*字段结构专用于弱参考机制。对象的构造函数应该保留它NULL(使用默认的tp_alloc时是自动的)
  2. tp_weaklistoffset type memberto设置为上述字段的偏移量在C对象结构中,以便解释器知道如何访问和修改该字段.

具体地说,这里是一个简单的对象结构如何用所需的字段增加:

typedef struct {
    PyObject_HEAD
    PyObject *weakreflist;  /* List of weak references */
} TrivialObject;

和相应的静态声明类型对象中的成员:

static PyTypeObject TrivialType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    /* ... other members omitted for brevity ... */
    .tp_weaklistoffset = offsetof(TrivialObject, weakreflist),
};

唯一的补充是tp_dealloc需要清除任何弱引用(通过调用PyObject_ClearWeakRefs()如果场是非 – NULL

static void
Trivial_dealloc(TrivialObject *self)
{
    /* Clear weakrefs first before calling any destructors */
    if (self->weakreflist != NULL)
        PyObject_ClearWeakRefs((PyObject *) self);
    /* ... remainder of destruction code omitted for brevity ... */
    Py_TYPE(self)->tp_free((PyObject *) self);
}

3.7。更多建议

为了学习如何为新数据类型实现任何特定方法,请获取 CPython 源代码。转到Objects目录,然后搜索C源文件tp_加上你想要的功能(例如tp_richcompare)。您将找到要实现的功能的示例.

当您需要验证对象是您要实现的类型的具体实例时,请使用PyObject_TypeCheck()函数。使用样本可能如下所示:

if (!PyObject_TypeCheck(some_object, &MyType)) {
    PyErr_SetString(PyExc_TypeError, "arg #1 not a mything");
    return NULL;
}

另见

下载CPython源代码发布.
https://www.python.org/downloads/source
/ GitHub上的CPython项目,开发了CPython源代码.
https://github.com/python/cpython