# 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. Керування загальними атрибутами

Більшість типів розширень використовують лише прості атрибути. Отже, що робить атрибути простими? Необхідно виконати лише кілька умов:

1. Під час виклику `PyType_Ready()` мають бути відомі назви атрибутів.

2. Ніякої спеціальної обробки не потрібно, щоб записати, що атрибут було знайдено або встановлено, а також не потрібно виконувати дії на основі значення.

Зауважте, що цей список не накладає жодних обмежень на значення атрибутів, час обчислення значень або спосіб зберігання відповідних даних.

Коли викликається `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
--- | ---
`READONLY` | Never writable.
`PY_AUDIT_READ` | Emit an `object.__getattr__` audit events before reading.

Змінено в версії 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` обробник викликається.

Ця функція приймає три аргументи:

1. self — це екземпляр типу даних, який є предметом виклику. Якщо виклик `obj1('hello')`, то self є `obj1`.

2. args — це кортеж, що містить аргументи виклику. Ви можете використовувати `PyArg_ParseTuple()`, щоб отримати аргументи.

3. 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:

1. 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 default `tp_alloc`).

2. 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.

  https://www.python.org/downloads/source/

Проект CPython на GitHub, де розробляється вихідний код CPython.

  https://github.com/python/cpython