Определение типов расширений: Разные темы
В этом разделе быстро рассматриваются различные методы типов, которые вы можете реализовать, и что они делают.
Вот определение PyTypeObject, с пропусками некоторых полей, используемых только в отладочных сборках:
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 */
/* 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__ для получения строки документации.
Теперь мы переходим к основным методам типа – тем, которые будут реализовывать большинство расширяемых типов.
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);
}
Важное требование к функции освобождения состоит в том, что она оставляет любые ожидающие исключения в покое. Это важно, так как функции освобождения часто вызываются при разворачивании стека 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(), он использует три таблицы, к которым обращается объект типа, для создания дескрипторов, которые помещаются в словарь объекта типа. Каждый дескриптор управляет доступом к одному атрибуту объекта экземпляра. Каждая из таблиц необязательна; если все три NULL, экземпляры типа будут иметь только атрибуты, унаследованные от базового типа, и должны оставить поля tp_getattro и tp_setattro NULL как и прежде, позволяя базовому типу обрабатывать атрибуты.
Таблицы объявлены как три поля объекта типа:
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;
Для каждого элемента в таблице будет создан и добавлен в тип дескриптор, который сможет извлечь значение из структуры экземпляра. Поле type должно содержать один из кодов типов, определённых в заголовке structmember.h; значение будет использоваться для определения того, как преобразовать значения Python в C-значения и обратно. Поле flags используется для хранения флагов, которые управляют тем, как к атрибуту можно получить доступ.
Следующие флаги констант определены в structmember.h; их можно комбинировать с помощью побитового ИЛИ.
Константа | Значение |
|---|---|
| Никогда не записываемый. |
| Не читаемый в режиме ограничения. |
| Не записываемый в режиме ограничения. |
| Не читаемый и не записываемый в режиме ограничения. |
Интересное преимущество использования таблицы tp_members для создания дескрипторов, используемых во время выполнения, заключается в том, что любой определённый таким образом атрибут может иметь связанный строку документации, просто предоставив текст в таблице. Приложение может использовать API интроспекции для извлечения дескриптора из объекта класса и получения строки документации, используя его атрибут __doc__.
Как и для таблицы tp_methods, требуется стоп-элемент со значением 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 вызывается при необходимости сравнения. Он аналогичен методам богатых сравнений, таким как __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()для извлечения аргументов. Если вы не хотите поддерживать ключевые аргументы и это не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;
}
%%%CODE_BLOCK_124%%<%p>Эти функции предоставляют поддержку для протокола итератора. Оба обработчика принимают ровно один параметр — экземпляр, для которого они вызываются, и возвращают новую ссылку. В случае ошибки они должны установить исключение и вернуть NULL. tp_iter соответствует методу Python __iter__(), а tp_iternext соответствует методу Python __next__().Любой объект итерируемый должен реализовывать обработчик tp_iter, который должен вернуть объект итератор. Здесь применяются те же рекомендации, что и для Python-классов:
- Для коллекций (таких как списки и кортежи), которые могут поддерживать несколько независимых итераторов, каждый вызов
tp_iterдолжен создавать и возвращать новый итератор. - Объекты, которые могут быть итерированы только один раз (обычно из-за побочных эффектов итерации, таких как объекты файлов), могут реализовать
tp_iter, вернув новую ссылку на себя — и, следовательно, также должны реализовывать обработчикtp_iternext.
Любой объект итератор должен реализовывать как tp_iter, так и tp_iternext. Обработчик итератора tp_iter должен вернуть новую ссылку на итератор. Его обработчик tp_iternext должен вернуть новую ссылку на следующий объект в итерации, если таковой имеется. Если итерация достигла конца, tp_iternext может вернуть NULL без установки исключения или установить StopIteration в дополнение к возвращению NULL; избежание исключения может немного улучшить производительность. Если возникнет фактическая ошибка, tp_iternext всегда должен установить исключение и вернуть NULL.
3.6. Поддержка слабых ссылок
Одной из целей реализации слабых ссылок в Python является предоставление возможности любому типу участвовать в механизме слабых ссылок без наложения издержек на объекты, критически важные для производительности (например, числа).
См. также
Документацию по модулю weakref.
Для того, чтобы объект мог быть слабо ссылаемым, тип расширения должен сделать две вещи:
- Включить поле
PyObject*в структуру C-объекта, предназначенное для механизма слабых ссылок. Конструктор объекта должен оставить егоNULL(что является автоматическим при использовании по умолчаниюtp_alloc). - Установить член типа
tp_weaklistoffsetв смещение указанного выше поля в структуре 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.
- Проект CPython на GitHub, где развивается исходный код CPython.
© 2001–2022 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.9/extending/newtypes.html