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__
для отримання рядка документа.
Тепер ми підходимо до методів базового типу — тих, які реалізовуватимуть більшість типів розширень.
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);
}
If no tp_repr
handler is specified, the interpreter will supply a
representation that uses the type’s tp_name
and a uniquely identifying
value for the object.
Обробник 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 supports two pairs of attribute handlers; a type that supports attributes only needs to implement the functions for one pair. The difference is that one pair takes the name of the attribute as a char*, while the other accepts a PyObject*. Each type can use whichever pair makes more sense for the implementation’s convenience.
getattrfunc tp_getattr; /* char * version */
setattrfunc tp_setattr;
/* ... */
getattrofunc tp_getattro; /* PyObject * version */
setattrofunc tp_setattro;
If accessing attributes of an object is always a simple operation (this will be explained shortly), there are generic implementations which can be used to provide the PyObject* version of the attribute management functions. The actual need for type-specific attribute handlers almost completely disappeared starting with Python 2.2, though there are many examples which have not been updated to use some of the new generic mechanism that is available.
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;
One entry should be defined for each method provided by the type; no entries are
needed for methods inherited from a base type. One additional entry is needed
at the end; it is a sentinel that marks the end of the array. The
ml_name
field of the sentinel must be NULL
.
Друга таблиця використовується для визначення атрибутів, які відображаються безпосередньо на дані, що зберігаються в екземплярі. Підтримуються різноманітні примітивні типи C, і доступ може бути лише для читання або читання-запису. Структури в таблиці визначені як:
typedef struct PyMemberDef {
const char *name;
int type;
int offset;
int flags;
const char *doc;
} PyMemberDef;
For each entry in the table, a descriptor will be constructed and added to the
type which will be able to extract a value from the instance structure. The
type
field should contain one of the type codes defined in the
structmember.h
header; the value will be used to determine how to
convert Python values to and from C values. The flags
field is used to
store flags which control how the attribute can be accessed.
The following flag constants are defined in structmember.h
; they may be
combined using bitwise-OR.
Constant |
Meaning |
---|---|
|
Never writable. |
|
Emit an |
Змінено в версії 3.10: RESTRICTED
, READ_RESTRICTED
and WRITE_RESTRICTED
are deprecated. However, READ_RESTRICTED
is an alias for
PY_AUDIT_READ
, so fields that specify either RESTRICTED
or READ_RESTRICTED
will also raise an audit event.
An interesting advantage of using the tp_members
table to build
descriptors that are used at runtime is that any attribute defined this way can
have an associated doc string simply by providing the text in the table. An
application can use the introspection API to retrieve the descriptor from the
class object, and get the doc string using its __doc__
attribute.
As with the tp_methods
table, a sentinel entry with a ml_name
value
of NULL
is required.
3.3.2. Типозалежне керування атрибутами¶
For simplicity, only the char* version will be demonstrated here; the type of the name parameter is the only difference between the char* and PyObject* flavors of the interface. This example effectively does the same thing as the generic example above, but does not use the generic support added in Python 2.2. It explains how the handler functions are called, so that if you do need to extend their functionality, you’ll understand what needs to be done.
The tp_getattr
handler is called when the object requires an attribute
look-up. It is called in the same situations where the __getattr__()
method of a class would be called.
Ось приклад:
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;
}
The tp_setattr
handler is called when the __setattr__()
or
__delattr__()
method of a class instance would be called. When an
attribute should be deleted, the third parameter will be NULL
. Here is an
example that simply raises an exception; if this were really all you wanted, the
tp_setattr
handler should be set to 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;
The tp_richcompare
handler is called when comparisons are needed. It is
analogous to the rich comparison methods, like
__lt__()
, and also called by PyObject_RichCompare()
and
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
is a signed integer type with a platform-varying width.
Returning -1
from tp_hash
indicates an error,
which is why you should be careful to avoid returning it when hash computation
is successful, as seen above.
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;
}
/* Iterators */
getiterfunc tp_iter;
iternextfunc tp_iternext;
These functions provide support for the iterator protocol. Both handlers
take exactly one parameter, the instance for which they are being called,
and return a new reference. In the case of an error, they should set an
exception and return NULL
. tp_iter
corresponds
to the Python __iter__()
method, while tp_iternext
corresponds to the Python __next__()
method.
Будь-який об’єкт 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
.
For an object to be weakly referencable, the extension type must do two things:
Include a PyObject* field in the C object structure dedicated to the weak reference mechanism. The object’s constructor should leave it
NULL
(which is automatic when using the defaulttp_alloc
).Set the
tp_weaklistoffset
type member to the offset of the aforementioned field in the C object structure, so that the interpreter knows how to access and modify that field.
Concretely, here is how a trivial object structure would be augmented with the required field:
typedef struct {
PyObject_HEAD
PyObject *weakreflist; /* List of weak references */
} TrivialObject;
And the corresponding member in the statically declared type object:
static PyTypeObject TrivialType = {
PyVarObject_HEAD_INIT(NULL, 0)
/* ... other members omitted for brevity ... */
.tp_weaklistoffset = offsetof(TrivialObject, weakreflist),
};
The only further addition is that tp_dealloc
needs to clear any weak
references (by calling PyObject_ClearWeakRefs()
) if the field is
non-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.