3. Определение типов расширений: Разные темы¶
В этом разделе мы кратко расскажем о различных методах типов, которые вы можете применять, и о том, что они делают.
Вот определение PyTypeObject
, в котором опущены некоторые поля, используемые только в debug builds:
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;
Py_ssize_t tp_vectorcall_offset;
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 */
/* Assigned meaning in release 2.0 */
/* call function for all accessible objects */
traverseproc tp_traverse;
/* delete references to contained objects */
inquiry tp_clear;
/* Assigned meaning in release 2.1 */
/* 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;
// Strong reference on a heap type, borrowed reference on a static type
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;
vectorcallfunc tp_vectorcall;
} 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((PyObject *)obj);
}
Если ваш тип поддерживает сборку мусора, деструктор должен вызвать PyObject_GC_UnTrack()
перед очисткой всех полей-членов:
static void
newdatatype_dealloc(newdatatypeobject *obj)
{
PyObject_GC_UnTrack(obj);
Py_CLEAR(obj->other_obj);
...
Py_TYPE(obj)->tp_free((PyObject *)obj);
}
Одно из важных требований к функции deallocator заключается в том, чтобы она оставляла в покое все ожидающие исключения. Это важно, поскольку деаллокаторы часто вызываются, когда интерпретатор разворачивает стек Python; когда стек разворачивается из-за исключения (а не из-за обычного возврата), ничего не делается для защиты деаллокаторов от того, что исключение уже было установлено. Любые действия, выполняемые деаллокатором, которые могут привести к выполнению дополнительного кода Python, могут обнаружить, что исключение было установлено. Это может привести к ошибкам, вводящим в заблуждение интерпретатор. Правильный способ защиты от этого - сохранить ожидающее исключение перед выполнением небезопасного действия и восстановить его по завершении. Это можно сделать с помощью функций 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_CallNoArgs(self->my_callback);
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);
}
Примечание
Существуют ограничения на то, что можно безопасно делать в функции деаллокатора. Во-первых, если ваш тип поддерживает сборку мусора (с помощью tp_traverse
и/или tp_clear
), то к моменту вызова tp_dealloc
некоторые члены объекта могут быть очищены или завершены. Во-вторых, в tp_dealloc
ваш объект находится в нестабильном состоянии: количество его ссылок равно нулю. Любое обращение к нетривиальному объекту или 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_repr
должен возвращать строковый объект, содержащий представление экземпляра, для которого он вызван. Вот простой пример:
static PyObject *
newdatatype_repr(newdatatypeobject * obj)
{
return PyUnicode_FromFormat("Repr-ified_newdatatype{{size:%d}}",
obj->obj_UnderlyingDatatypePtr->size);
}
Если обработчик tp_repr
не указан, интерпретатор предоставит представление, которое использует 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 поддерживает две пары обработчиков атрибутов; тип, поддерживающий атрибуты, должен реализовать функции только для одной пары. Разница в том, что одна пара принимает имя атрибута как 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. Общее управление атрибутами¶
Большинство типов расширений используют только простые атрибуты. Что же делает атрибуты простыми? Есть только пара условий, которые должны быть выполнены:
Имена атрибутов должны быть известны при вызове
PyType_Ready()
.Для записи того, что атрибут был найден или установлен, не требуется специальной обработки, равно как и действий, основанных на его значении.
Обратите внимание, что этот список не накладывает никаких ограничений на значения атрибутов, время их вычисления или способ хранения соответствующих данных.
Когда вызывается PyType_Ready()
, он использует три таблицы, на которые ссылается объект типа, для создания descriptor, которые помещаются в словарь объекта типа. Каждый дескриптор управляет доступом к одному атрибуту объекта экземпляра. Каждая из таблиц необязательна; если все три NULL
, экземпляры типа будут иметь только атрибуты, унаследованные от базового типа, и должны оставить поля tp_getattro
и tp_setattro
NULL
, позволяя базовому типу работать с атрибутами.
Таблицы объявляются как три поля типа object:
struct PyMethodDef *tp_methods;
struct PyMemberDef *tp_members;
struct PyGetSetDef *tp_getset;
Если tp_methods
не NULL
, то он должен ссылаться на массив структур PyMethodDef
. Каждая запись в таблице является экземпляром этой структуры:
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;
Для каждой записи в таблице будет сконструирован и добавлен к типу descriptor, который сможет извлекать значение из структуры экземпляра. Поле type
должно содержать один из кодов типов, определенных в заголовке structmember.h
; это значение будет использоваться для определения способа преобразования значений Python в значения C и обратно. Поле flags
используется для хранения флагов, которые управляют доступом к атрибуту.
Следующие константы флагов определены в structmember.h
; они могут быть объединены с помощью побитового ИЛИ.
Постоянно |
Значение |
---|---|
|
Не подлежит записи. |
|
Перед чтением выдается сигнал |
Изменено в версии 3.10: RESTRICTED
, READ_RESTRICTED
и WRITE_RESTRICTED
устарели. Однако READ_RESTRICTED
является псевдонимом для PY_AUDIT_READ
, поэтому поля, в которых указаны либо RESTRICTED
, либо READ_RESTRICTED
, также будут вызывать событие аудита.
Интересным преимуществом использования таблицы tp_members
для создания дескрипторов, используемых во время выполнения, является то, что любой атрибут, определенный таким образом, может иметь связанную с ним doc-строку, просто указав текст в таблице. Приложение может использовать API интроспекции для получения дескриптора из объекта класса и получить строку doc с помощью его атрибута __doc__
.
Как и в случае с таблицей tp_methods
, требуется запись-дозорный со значением ml_name
NULL
.
3.3.2. Управление атрибутами конкретного типа¶
Для простоты здесь будет показана только версия char*; тип параметра name - единственное различие между интерфейсами char* и PyObject*. Этот пример фактически делает то же самое, что и приведенный выше общий пример, но не использует общую поддержку, добавленную в Python 2.2. В нем объясняется, как вызываются функции-обработчики, так что если вам понадобится расширить их функциональность, вы поймете, что нужно сделать.
Обработчик tp_getattr
вызывается, когда объект требует поиска атрибута. Он вызывается в тех же ситуациях, когда был бы вызван метод __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__()
экземпляра класса. Когда атрибут должен быть удален, третьим параметром будет 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
вызывается, когда необходимо провести сравнение. Он аналогичен rich comparison methods, как и __lt__()
, а также вызывается PyObject_RichCompare()
и PyObject_RichCompareBool()
.
Эта функция вызывается с двумя объектами Python и оператором в качестве аргументов, где оператор является одним из Py_EQ
, Py_NE
, Py_LE
, Py_GE
, Py_LT
или Py_GT
. Она должна сравнить два объекта относительно указанного оператора и вернуть Py_True
или Py_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 с самого начала. Другие протоколы были добавлены со временем. Для протоколов, которые зависят от нескольких процедур-обработчиков из реализации типа, старые протоколы были определены как необязательные блоки обработчиков, на которые ссылается объект типа. Для более новых протоколов в основном объекте типа имеются дополнительные слоты, при этом устанавливается бит флага, указывающий на то, что слоты присутствуют и должны быть проверены интерпретатором. (Бит флага не указывает на то, что значения слотов не``NULL``. Флаг может быть установлен, чтобы указать на наличие слота, но слот может быть незаполнен).
PyNumberMethods *tp_as_number;
PySequenceMethods *tp_as_sequence;
PyMappingMethods *tp_as_mapping;
Если вы хотите, чтобы ваш объект мог действовать как число, последовательность или объект отображения, то вы помещаете адрес структуры, реализующей тип C PyNumberMethods
, PySequenceMethods
или PyMappingMethods
соответственно. Вам предстоит заполнить эту структуру соответствующими значениями. Примеры использования каждого из них можно найти в каталоге Objects
исходного дистрибутива Python.
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
.
Эта функция принимает три аргумента:
self - это экземпляр типа данных, который является объектом вызова. Если вызов
obj1('hello')
, то self - этоobj1
.args - кортеж, содержащий аргументы вызова. Вы можете использовать
PyArg_ParseTuple()
для извлечения аргументов.kwds - это словарь аргументов ключевых слов, которые были переданы. Если это не``NULL`` и вы поддерживаете аргументы ключевых слов, используйте
PyArg_ParseTupleAndKeywords()
для извлечения аргументов. Если вы не хотите поддерживать аргументы ключевых слов и this не``NULL``, вызовите ошибкуTypeError
с сообщением о том, что аргументы ключевых слов не поддерживаются.
Вот игрушечная tp_call
реализация:
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
, который должен возвращать объект iterator. Здесь действуют те же рекомендации, что и для классов Python:
Для коллекций (таких как списки и кортежи), которые могут поддерживать несколько независимых итераторов, при каждом вызове
tp_iter
должен быть создан и возвращен новый итератор.Объекты, которые могут быть итерированы только один раз (обычно из-за побочных эффектов итерации, например, файловые объекты), могут реализовать
tp_iter
, возвращая новую ссылку на себя - и поэтому также должны реализовать обработчикtp_iternext
.
Любой объект iterator должен реализовывать как tp_iter
, так и tp_iternext
. Обработчик tp_iter
итератора должен возвращать новую ссылку на итератор. Его обработчик tp_iternext
должен возвращать новую ссылку на следующий объект в итерации, если таковой имеется. Если итерация достигла конца, tp_iternext
может вернуть NULL
без создания исключения или установить StopIteration
в дополнение к возврату NULL
; избежание исключения может дать немного лучшую производительность. Если произошла фактическая ошибка, tp_iternext
всегда должен установить исключение и вернуть NULL
.
3.6. Слабая справочная поддержка¶
Одна из целей реализации слабых ссылок в Python - позволить любому типу участвовать в механизме слабых ссылок без накладных расходов на критичные к производительности объекты (например, числа).
См.также
Документация для модуля weakref
.
Чтобы объект был слабо ссылаемым, тип расширения должен выполнять две вещи:
Включите в структуру объекта C поле PyObject*, предназначенное для механизма слабых ссылок. Конструктор объекта должен оставлять его
NULL
(что происходит автоматически при использовании значения по умолчаниюtp_alloc
).Установите член типа
tp_weaklistoffset
на смещение вышеупомянутого поля в структуре объекта C, чтобы интерпретатор знал, как обращаться к этому полю и изменять его.
Конкретно, вот как тривиальная структура объекта может быть дополнена необходимым полем:
typedef struct {
PyObject_HEAD
PyObject *weakreflist; /* List of weak references */
} TrivialObject;
И соответствующий член в статически объявленном типе object:
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.
- Проект CPython на GitHub, где разрабатывается исходный код CPython.