Определение типов расширений: различные темы
В этом разделе кратко рассматриваются различные методы типов, которые можно реализовать, и их назначение.
Вот определение 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 */
/* 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;
/* bitset of which type-watchers care about this type */
unsigned char tp_watched;
} 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 должно содержать код типа, например Py_T_INT или Py_T_DOUBLE; по этому значению определяется способ преобразования значений Python в значения C и обратно. Поле flags используется для хранения флагов, управляющих доступом к атрибуту: установите его в Py_READONLY, чтобы запретить коду Python изменять атрибут.
Интересное преимущество использования таблицы tp_members для создания дескрипторов, используемых во время выполнения, заключается в том, что любой атрибут, определённый таким образом, может иметь связанную строку документации — достаточно указать текст в таблице. Приложение может использовать API интроспекции, чтобы получить дескриптор из объекта класса и прочитать строку документации через его атрибут __doc__.
Как и для таблицы tp_methods, требуется запись-маркер со значением NULL в поле ml_name.
3.3.2. Управление атрибутами, специфичное для типа
Для простоты здесь будет показан только вариант с char*; единственное различие между вариантами интерфейса с 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,
"'%.100s' object has no attribute '%.400s'",
Py_TYPE(obj)->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(newdatatypeobject *obj1, newdatatypeobject *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 *obj, 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__().
Любой итерируемый объект должен реализовать обработчик 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.
Чтобы на объект можно было создать слабую ссылку, тип расширения должен установить бит Py_TPFLAGS_MANAGED_WEAKREF поля tp_flags. Устаревшее поле tp_weaklistoffset следует оставить равным нулю.
Вот как должен выглядеть статически объявленный объект типа:
static PyTypeObject TrivialType = {
PyVarObject_HEAD_INIT(NULL, 0)
/* ... other members omitted for brevity ... */
.tp_flags = Py_TPFLAGS_MANAGED_WEAKREF | ...,
};
Единственное дополнительное изменение: tp_dealloc должен очищать все слабые ссылки (вызывая PyObject_ClearWeakRefs()):
static void
Trivial_dealloc(TrivialObject *self)
{
/* Clear weakrefs first before calling any destructors */
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–2025 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.13/extending/newtypes.html