> **Источник:** https://python-all.ru/3.16/howto/abi3t-migration.html
>
> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.

---

# Миграция на стабильный ABI для свободной многопоточности (`abi3t`)

Начиная с версии 3.15, CPython поддерживает вариант стабильного ABI, который поддерживает [со свободной многопоточностью](https://python-all.ru/3.16/glossary.html#term-free-threading) Python: стабильный ABI для сборок со свободной многопоточностью, или сокращённо `abi3t`. В этом документе описывается, как адаптировать расширения C API для поддержки свободной многопоточности.

## Зачем это нужно

Обычная причина использования стабильного ABI – сократить количество артефактов, которые необходимо собирать и распространять для каждой версии своей библиотеки.

Без стабильного ABI необходимо собирать отдельную разделяемую библиотеку и, как правило, дистрибутив *wheel* для каждой функциональной версии CPython, которую требуется поддерживать. Например, каждый тег в следующей таблице представляет отдельную библиотеку/wheel:

| Версия CPython | Без свободной многопоточности | Со свободной многопоточностью |
| --- | --- | --- |
| 3.12 | `cpython-312` | – |
| 3.13 | `cpython-313` | `cpython-313t` |
| 3.14 | `cpython-314` | `cpython-314t` |
| 3.15 | `cpython-315` | `cpython-315t` |
| 3.16 | `cpython-316` | `cpython-316t` |
| Более поздние версии | `cpython-3XX` | `cpython-3XXt` |

Это много сборок, особенно если умножить на количество поддерживаемых платформ.

Благодаря стабильному ABI (`abi3`, представленному в CPython 3.2), одно расширение (на платформу) может покрывать все сборки *без свободной потоковости* CPython:

| Версия CPython | Без свободной потоковости | Со свободной потоковостью |
| --- | --- | --- |
| 3.12 | `abi3` | – |
| 3.13 | `cpython-313t` |  |
| 3.14 | `cpython-314t` |  |
| 3.15 | `cpython-315t` |  |
| 3.16 | `cpython-316t` |  |
| Более поздние версии | `cpython-3XXt` |  |

Стабильный ABI для сборок со свободной потоковостью (`abi3t`), представленный в CPython 3.15, делает то же самое для сборок со свободной потоковостью. И он также совместим со сборками без свободной потоковости:

| Версия CPython | Без свободной потоковости | Со свободной потоковостью |
| --- | --- | --- |
| 3.12 | `abi3` \* | – |
| 3.13 | `cpython-313t` |  |
| 3.14 | `cpython-314t` |  |
| 3.15 | `abi3t` |  |
| 3.16 |  |  |
| Более поздние версии |  |  |

\* (Как и выше, расширение `abi3` совместимо со всеми сборками без свободной потоковости; даже с теми версиями 3.15+, которые эта таблица «приписывает» `abi3t`.)

### Почему бы *не* сделать это

У стабильного ABI есть два основных недостатка.

Во-первых, ваше расширение может работать медленнее, так как стабильный ABI ставит совместимость выше производительности. Разница обычно незаметна, и её часто можно смягчить, используя один и тот же исходный код для сборки как со стабильным ABI, так и нескольких версионно-специфичных сборок для «основных» версий CPython.

Во-вторых, доступна не вся часть C API. Расширения необходимо портировать для сборки со стабильным ABI, что может быть сложно или, в редких случаях, невозможно.

В частности, `abi3t` требует API, добавленных в CPython 3.15. Если вы хотите собирать расширение для более старых версий CPython из одного исходного кода, у вас есть два основных варианта:

- Используйте условные конструкции препроцессора.

  При следовании этому руководству используйте блоки `#ifdef Py_TARGET_ABI3T` всякий раз, когда вам говорят внести изменение, которое нарушает сборку на интересующих вас версиях CPython. Оставляйте существующий код в блоках `#else`.

  Для написанных вручную расширений C такой подход разумен вплоть до CPython 3.12, благодаря дополнениям, введённым в [**PEP 697**](https://python-all.ru/3.16/howto/abi3t-migration.html). Сохранение совместимости с 3.11 и ниже может быть оправдано для генераторов кода (например, Cython).
- Не портируйте на `abi3t` и продолжайте собирать отдельные расширения для каждой версии CPython, пока не сможете отказаться от поддержки старых версий.

  Это допустимый подход. Не всем расширениям нужно переходить на `abi3t` прямо сейчас.

## Предварительные требования

В этом руководстве предполагается, что у вас есть расширение, написанное непосредственно на C (или C++), которое вы хотите портировать на `abi3t`.

Если ваше расширение использует генератор кода (например, Cython) или языковую привязку (например, PyO3), лучше подождать, пока этот инструмент не получит поддержку `abi3t`. Если вы поддерживаете такой инструмент, возможно, вы сможете адаптировать инструкции из этого руководства для своего инструмента.

### Стабильный ABI без свободной потоковости

Ваше расширение должно поддерживать стабильный ABI (`abi3t`). Если нет, либо сначала портируйте его, либо следуйте этому руководству, но будьте готовы исправить проблемы, которые в нём не упомянуты.

### Поддержка свободной потоковости

Хотя технически это не строгое требование, скорее всего, вы захотите подготовить расширение для свободной потоковости, прежде чем портировать его на `abi3t`. См. [Поддержка расширений C API для свободной потоковости](https://python-all.ru/3.16/howto/free-threading-extensions.html#freethreading-extensions-howto) за инструкциями.

> **См. также**
>
> [Портирование модулей расширений для поддержки свободной потоковости](https://python-all.ru/3.16/howto/abi3t-migration.html): Руководство по портированию, поддерживаемое сообществом, для авторов расширений.

### Изоляция модулей расширений

Ваш модуль должен использовать [многофазную инициализацию](https://python-all.ru/3.16/c-api/extension-modules.html#multi-phase-initialization), и должен быть либо изолированным, либо ограничиваться загрузкой не более одного раза на процесс. Если это не ваш случай, сначала следуйте [разделу «Изоляция расширений модулей»](https://python-all.ru/3.16/howto/isolating-extensions.html#isolating-extensions-howto). (См. [раздел об отказе](https://python-all.ru/3.16/howto/isolating-extensions.html#isolating-extensions-optout) для быстрого решения.)

### Избегание типов переменного размера

Если ваше расширение определяет типы переменного размера (с помощью [`Py_tp_itemsize`](https://python-all.ru/3.16/c-api/type.html#c.Py_tp_itemsize) или [`PyTypeObject.tp_itemsize`](https://python-all.ru/3.16/c-api/typeobj.html#c.PyTypeObject.tp_itemsize)), его невозможно портировать на `abi3t` 3.15.

## Настройка сборки

Если вы используете инструмент сборки (например, setuptools, meson-python, scikit-build-core), найдите в его документации способ выбрать `abi3t`. На момент написания не все они это поддерживают; но если ваш инструмент поддерживает, используйте его. Возможно, вам стоит проверить, что он установил правильный флаг, временно добавив следующий код сразу после `#include <Python.h>`:

```c
#if Py_TARGET_ABI3T+0 <= 0x30f0000
#error "abi3t define is not set!"
#endif
```

Это должно привести к другой ошибке, нежели «`abt3t` define is not set».

> **Примечание**
>
> Если ваш инструмент сборки ещё не поддерживает `abi3t`, установите следующий макрос перед включением `Python.h`:
>
> ```c
> #define Py_TARGET_ABI3T 0x30f0000
> ```
>
> или укажите его как флаг компилятора, например:
>
> ```c
> -DPy_TARGET_ABI3T=0x30f0000
> ```
>
> Как только ваше расширение будет собираться с этой настройкой, оно станет совместимым с CPython 3.15 и выше.
>
> Если вы устанавливаете этот макрос вручную, вам также потребуется вручную назвать и пометить полученное расширение. Это описано в разделе [«Пометка и распространение»](https://python-all.ru/3.16/howto/abi3t-migration.html#abi3t-migration-tagging) ниже.

В этом руководстве вам будет предложено выполнить ряд изменений. После каждого изменения проверяйте, что ваше расширение по-прежнему собирается в исходной (не-`abi3t`) конфигурации, и желательно запускайте тесты на всех поддерживаемых вами версиях Python. Это гарантирует, что в процессе портирования ничего не сломается.

## Хук экспорта модуля

Если вы ещё не выполнили этот шаг, ваш модуль расширения определяет [функцию инициализации модуля](https://python-all.ru/3.16/c-api/extension-modules.html#extension-pyinit) с именем `PyInit_<module_name>`. Вам нужно будет перенести её в [хук экспорта модуля](https://python-all.ru/3.16/c-api/extension-modules.html#extension-export-hook), `PyModExport_<module name>`, функцию, добавленную в CPython 3.15 в [**PEP 793**](https://python-all.ru/3.16/howto/abi3t-migration.html).

Ваша существующая функция init должна выглядеть примерно так (с вашими собственными именами для `<modname>` и `<moddef>`):

```c
PyMODINIT_FUNC
PyInit_<modname>(void)
{
    return PyModuleDef_Init(&<moddef>);
}
```

Если перед `return` есть какой-то код, переместите его в слот-функцию [`Py_mod_create`](https://python-all.ru/3.16/c-api/module.html#c.Py_mod_create) или [`Py_mod_exec`](https://python-all.ru/3.16/c-api/module.html#c.Py_mod_exec). См. [документацию PyInit](https://python-all.ru/3.16/c-api/extension-modules.html#extension-pyinit) для получения дополнительной информации.

Функция ссылается на объект `PyModuleDef` (`<moddef>` в коде выше). Его определение должно быть похоже на следующее, с другими значениями и, возможно, некоторыми неименованными или пропущенными полями:

```c
static PyModuleDef <moddef> = {
    PyModuleDef_HEAD_INIT,
    .m_name = "my_module",
    .m_doc = "my docstring",
    .m_size = sizeof(my_state_struct),
    .m_methods = my_methods,
    .m_slots = my_slots,
    .m_traverse = my_traverse,
    .m_clear = my_clear,
    .m_free = my_free,
};
```

Удалите это определение и функцию `PyInit` (или поместите их в блок `#ifndef Py_TARGET_ABI3T` для сохранения обратной совместимости), и замените их следующим:

```c
PyABIInfo_VAR(abi_info);

static PySlot my_slot_array[] = {
    PySlot_STATIC_DATA(Py_mod_abi, &abi_info),
    PySlot_STATIC_DATA(Py_mod_name, "my_module"),
    PySlot_STATIC_DATA(Py_mod_doc, "my docstring"),
    PySlot_SIZE(Py_mod_state_size, sizeof(my_state_struct)),
    PySlot_STATIC_DATA(Py_mod_methods, my_methods),
    PySlot_STATIC_DATA(Py_mod_slots, my_slots),
    PySlot_FUNC(Py_mod_state_traverse, my_traverse),
    PySlot_FUNC(Py_mod_state_clear, my_clear),
    PySlot_FUNC(Py_mod_state_free, my_free),
    PySlot_END
};

PyMODEXPORT_FUNC
PyModExport_<modname>(void)
{
    return my_slot_array;
}
```

Опустите любые поля, которые отсутствовали (кроме нового [`Py_mod_abi`](https://python-all.ru/3.16/c-api/module.html#c.Py_mod_abi)), и подставьте свои собственные значения.

См. документацию [`PySlot`](https://python-all.ru/3.16/c-api/slots.html#c.PySlot) и [хука экспорта](https://python-all.ru/3.16/c-api/extension-modules.html#extension-export-hook) для получения подробностей об этом API.

Как в примере, ваша функция `PyModExport_` должна *только* возвращать указатель на статические данные. Если вы не можете избежать дополнительного кода, обратитесь к [предостережениям в документации PyModExport](https://python-all.ru/3.16/c-api/extension-modules.html#pymodexport-api-caveats).

### Существующие слоты

Если у вас есть слот `Py_mod_slots`, проверьте массив, на который он ссылается. Это должен быть массив [`PyModuleDef_Slot`](https://python-all.ru/3.16/c-api/module.html#c.PyModuleDef_Slot), например:

```c
static PyObject *create_module(PyObject *spec, PyModuleDef *def) { ... }
static int my_first_module_exec(PyObject *module) { ... }
static int my_second_module_exec(PyObject *module) { ... }

static PyModuleDef_Slot my_slots[] = {
   {Py_mod_gil, Py_MOD_GIL_NOT_USED},
   {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
   {Py_mod_create, my_module_create},
   {Py_mod_exec, my_first_module_exec},
   {Py_mod_exec, my_second_module_exec},
   {0, NULL}
};
```

#### `py_mod_create`

Если у вас есть запись [`Py_mod_create`](https://python-all.ru/3.16/c-api/module.html#c.Py_mod_create), убедитесь, что функцию можно вызвать с `NULL` в качестве второго аргумента (вместо [`PyModuleDef`](https://python-all.ru/3.16/c-api/module.html#c.PyModuleDef), который вы удаляете). Часто этот аргумент вообще не используется; вы можете проверить, переименовав его:

```c
static PyObject *create_module(PyObject *spec, PyModuleDef *_unused) { ... }
```

Если аргумент используется, найдите другой способ передачи данных. Обычно информация статична, и вы можете обращаться к ней напрямую. (Если вы используете одну функцию для нескольких разных модулей, рассмотрите возможность определить несколько функций вместо этого.)

#### Несколько `py_mod_exec`

Если у вас есть *более одной* записи [`Py_mod_exec`](https://python-all.ru/3.16/c-api/module.html#c.Py_mod_exec), объедините их: создайте новую функцию, которая вызывает остальные, и замените ею существующие слоты.

```c
static int my_module_exec(PyObject *module) {
   if (my_first_module_exec(module) < 0) return -1;
   if (my_second_module_exec(module) < 0) return -1;
}

static PyModuleDef_Slot my_slots[] = {
   ...
   /* удалить остальные слоты Py_mod_exec */
   ...
   {Py_mod_exec, my_module_exec},
   {0, NULL}
};
```

Если функции нигде больше не используются, вы можете вместо этого объединить их тела.

#### Объединение массивов слотов

Опционально, когда вы нарушаете совместимость с Python 3.14, вы можете очистить код, переместив слоты в массив [`PySlot`](https://python-all.ru/3.16/c-api/slots.html#c.PySlot) и преобразовав определения в [`PySlot_DATA`](https://python-all.ru/3.16/c-api/slots.html#c.PySlot_DATA) и [`PySlot_FUNC`](https://python-all.ru/3.16/c-api/slots.html#c.PySlot_FUNC):

```c
static PySlot my_slot_array[] = {
    ...
    PySlot_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED),
    PySlot_DATA(Py_mod_multiple_interpreters,
         Py_MOD_PER_INTERPRETER_GIL_SUPPORTED)
    PySlot_FUNC(Py_mod_create, my_module_create),
    PySlot_FUNC(Py_mod_exec, my_module_exec),
    PySlot_END
};
```

Если вы это сделаете, удалите исходный массив [`PyModuleDef_Slot`](https://python-all.ru/3.16/c-api/module.html#c.PyModuleDef_Slot) и его запись `Py_mod_slots`.

### Связанные `PyModuleDef`

Поскольку новый API не использует структуру `PyModuleDef`, определение не будет связано с результирующим модулем. Это изменяет поведение следующих функций:

- [`PyModule_GetDef()`](https://python-all.ru/3.16/c-api/module.html#c.PyModule_GetDef)
- [`PyType_GetModuleByDef()`](https://python-all.ru/3.16/c-api/type.html#c.PyType_GetModuleByDef)

Проверьте свой код на наличие этих функций. Если вы их не используете, можете пропустить этот раздел.

Обычно эти функции применяются для двух целей:

1. Получить определение, с которым был создан модуль. В новом API это больше невозможно. Модули больше не хранят ссылку на определение, поэтому придётся найти другой способ передавать нужные данные.

2. Проверить, «ваш» ли данный объект модуля. Теперь эту задачу решают [токены модулей](https://python-all.ru/3.16/c-api/module.html#ext-module-token) – непрозрачные указатели, идентифицирующие модуль. Чтобы использовать токен, объявите (или переиспользуйте) уникальную статическую переменную, например:

   ```c
   static char my_token;
   ```

   и добавьте указатель на неё в новую запись массива `PySlot` вашего модуля:

   ```c
   static PySlot my_slot_array[] = {
      ...
      PySlot_STATIC_DATA(Py_mod_token, &my_token),
      PySlot_END
   }
   ```

   Затем перейдите от вызовов [`PyModule_GetDef()`](https://python-all.ru/3.16/c-api/module.html#c.PyModule_GetDef) вида:

   ```c
   PyModuleDef *def = PyModule_GetDef(module);
   ```

   к [`PyModule_GetToken()`](https://python-all.ru/3.16/c-api/module.html#c.PyModule_GetToken) (которая использует выходной аргумент и может завершиться исключением):

   ```c
   void *token;
   if (PyModule_GetToken(module, &token) < 0) {
       /* обработать ошибку */
   }
   ```

   и от вызовов [`PyType_GetModuleByDef()`](https://python-all.ru/3.16/c-api/type.html#c.PyType_GetModuleByDef) вида:

   ```c
   PyObject *module = PyType_GetModuleByDef(type, my_def);
   /* обработать ошибку; использовать модуль */
   ```

   к [`PyType_GetModuleByToken()`](https://python-all.ru/3.16/c-api/type.html#c.PyType_GetModuleByToken) (которая возвращает сильную ссылку):

   ```c
   PyObject *module = PyType_GetModuleByToken(type, my_token);
   /* обработать ошибку; использовать модуль */
   Py_XDECREF(module);
   ```

## `PyObject` непрозрачность

Структуры [`PyObject`](https://python-all.ru/3.16/c-api/structures.html#c.PyObject) и [`PyVarObject`](https://python-all.ru/3.16/c-api/structures.html#c.PyVarObject) являются непрозрачными в `abi3t`.

Обращаться к их членам запрещено. Если вы это делали, переключитесь на функции-геттеры/сеттеры, упомянутые в их документации:

- [`PyObject.ob_type`](https://python-all.ru/3.16/c-api/structures.html#c.PyObject.ob_type)
- [`PyObject.ob_refcnt`](https://python-all.ru/3.16/c-api/structures.html#c.PyObject.ob_refcnt)
- [`PyVarObject.ob_size`](https://python-all.ru/3.16/c-api/structures.html#c.PyVarObject.ob_size)

Кроме того, компилятору неизвестен *размер* структур [`PyObject`](https://python-all.ru/3.16/c-api/structures.html#c.PyObject). Он может – и *действительно* – изменяться между разными сборками CPython.

> **Примечание**
>
> Хотя размер доступен во время выполнения (например, как `sys.getsizeof(object())` в коде Python), не стоит поддаваться искушению вычислять по нему смещения указателей. Расположение полей объекта может измениться в будущих реализациях `abi3t`.

### Определения пользовательских типов

Поскольку `PyObject` непрозрачен, традиционный способ определения пользовательских типов больше не работает:

```c
typedef struct {
   PyObject_HEAD  // раскрывается в `PyObject ob_base;` с неизвестным размером

   int my_data;
} CustomObject;

static PyType_Spec CustomType_spec = {
   ...
   .basicsize = sizeof(CustomObject),
   ...
};
```

Скорее всего, все определения ваших классов *и* весь код, обращающийся к данным этих классов, нужно будет переписать. Вероятно, это самое большое изменение, которое потребуется для поддержки `abi3t`.

Для каждого такого типа вместо определения `struct` для всего экземпляра определите его только с «дополнительными» полями – теми, что относятся к вашему классу, а не к его суперклассам:

```c
typedef struct {
   int my_data;
} CustomObjectData;
```

Измените имя. Почти весь код, использующий эту структуру, нужно будет изменить (в частности, указатели на новую структуру нельзя просто приводить к `PyObject*` и обратно), а смена имени позволит подсветить все места использования как ошибки компилятора. (Если вы используете `typeof`, C++ `auto` или подобные способы, чтобы не писать имя типа, это не сработает. Будьте особенно внимательны и рассмотрите запуск инструментов для поиска неопределённого поведения.)

Затем для создания класса используйте *отрицательное* значение `basicsize`, чтобы указать «дополнительное» пространство для хранения, а не *общий* размер экземпляра:

```c
static PyType_Spec CustomType_spec = {
   ...
   .basicsize = -sizeof(CustomObjectData), /* обратите внимание на знак минус */
   ...
};
```

Если вы используете [`Py_tp_members`](https://python-all.ru/3.16/c-api/typeobj.html#c.Py_tp_members), установите флаг [`Py_RELATIVE_OFFSET`](https://python-all.ru/3.16/c-api/structures.html#c.Py_RELATIVE_OFFSET) для каждого члена и укажите [`offset`](https://python-all.ru/3.16/c-api/structures.html#c.PyMemberDef.offset) относительно вашей новой структуры.

### Доступ к данным пользовательского типа

Далее начинается самое сложное: во всём коде, которому нужен доступ к этой структуре, потребуется дополнительный вызов [`PyObject_GetTypeData()`](https://python-all.ru/3.16/c-api/object.html#c.PyObject_GetTypeData) для получения указателя `CustomObjectData *` из `PyObject *`:

```c
PyObject *obj = ...;
CustomObjectData *data = PyObject_GetTypeData(obj, cls);
```

Обратите внимание: этот вызов требует *объект типа* вашего класса (`cls`).

Если ваш класс не допускает наследования (то есть не использует флаг [`Py_TPFLAGS_BASETYPE`](https://python-all.ru/3.16/c-api/typeobj.html#c.Py_TPFLAGS_BASETYPE)), то `cls` будет `Py_TYPE(obj)`. В противном случае **НЕ ИСПОЛЬЗУЙТЕ** `Py_TYPE` с `PyObject_GetTypeData()`: он может вернуть память, зарезервированную для постороннего подкласса! Например, если пользователь создаст такой подкласс:

```python
class Sub(YourCustomClass):
   __slots__ = ('a', 'b')
```

то `Py_TYPE(obj)` равно `Sub`, и лежащая в основе память может выглядеть так:

```text
╭─ PyObject *obj
│              ╭─ the pointer you want
│              │                    ╭─ PyObject_GetTypeData(obj, Py_TYPE(obj))
▼              ▼                    ▼
┌──────────┬───┬────────────────┬───┬─────────────┬───┬─────────────┐
│ PyObject │...│ CustomTypeData │...│ PyObject *a │...│ PyObject *b │
└──────────┴───┴────────────────┴───┴─────────────┴───┴─────────────┘
```

(Многоточия обозначают возможное выравнивание. Обратите внимание: такое расположение в памяти не гарантируется – будущие версии Python могут добавить другое выравнивание или даже изменить порядок структур.)

Есть два основных способа получить правильный класс:

- В методах экземпляра ваша реализация может использовать сигнатуру [`PyCMethod`](https://python-all.ru/3.16/c-api/structures.html#c.PyCMethod) (и бит [`METH_METHOD`](https://python-all.ru/3.16/c-api/structures.html#c.METH_METHOD) в [`PyMethodDef.ml_flags`](https://python-all.ru/3.16/c-api/structures.html#c.PyMethodDef.ml_flags)), получая класс как аргумент `defining_class`.
- В противном случае дайте вашему классу уникальный статический токен через слот [`Py_tp_token`](https://python-all.ru/3.16/c-api/type.html#c.Py_tp_token) и используйте:

  ```c
  PyTypeObject cls;
  if (PyType_GetBaseByToken(Py_TYPE(obj), my_tp_token, &cls) < 0) {
      /* обработать ошибку */
  }
  CustomObjectData *data = PyObject_GetTypeData(obj, cls);
  ```

  Токены типов работают так же, как токены модулей, описанные [ранее в этом руководстве](https://python-all.ru/3.16/howto/abi3t-migration.html#abi3t-migration-module-token).

## Избегайте условной компиляции

Проверьте свой код на наличие API, определяющего версию Python, под которую *собран* ваш модуль расширения. Больше это не соответствует той версии Python, на которой модуль выполняется, поэтому такой код часто требует изменений. Ищите следующие макросы:

- [`PY_VERSION_HEX`](https://python-all.ru/3.16/c-api/apiabiversion.html#c.PY_VERSION_HEX), [`PY_MAJOR_VERSION`](https://python-all.ru/3.16/c-api/apiabiversion.html#c.PY_MAJOR_VERSION), [`PY_MINOR_VERSION`](https://python-all.ru/3.16/c-api/apiabiversion.html#c.PY_MINOR_VERSION):

  - Чтобы получить версию времени выполнения, используйте [`Py_Version`](https://python-all.ru/3.16/c-api/apiabiversion.html#c.Py_Version);
  - Чтобы определить, какой C API доступен, используйте [`Py_TARGET_ABI3T`](https://python-all.ru/3.16/c-api/stable.html#c.Py_TARGET_ABI3T). Этот макрос устанавливается в минимальную поддерживаемую версию.
- [`Py_GIL_DISABLED`](https://python-all.ru/3.16/using/configure.html#c.Py_GIL_DISABLED): в `abi3t` этот макрос всегда определён. Код, работающий со свободно-поточной версией Python, *должен* также работать и с включённым GIL (поскольку GIL может быть включён во время выполнения), и обычно *работает* (если только по какой-то причине ему не требуется более одного [прикреплённого состояния потока](https://python-all.ru/3.16/glossary.html#term-attached-thread-state) одновременно).

## Дальнейшие изменения кода

Если у вас всё ещё остаются ошибки или предупреждения компилятора, найдите способ их исправить. Увы, это руководство ограничено и не может охватить все возможные изменения кода, которые могут потребоваться расширениям.

Если вы столкнулись с проблемой, с которой могут встретиться другие авторы расширений, рассмотрите возможность [сообщить о проблеме](https://python-all.ru/3.16/bugs.html#reporting-documentation-bugs) (или отправить pull request) для этого руководства.

Возможно, вашу проблему невозможно исправить в текущей версии `abi3t`. В таком случае сообщение о ней может помочь расставить приоритеты для следующей версии CPython.

## Тегирование и распространение

Если вы используете инструмент сборки с поддержкой `abi3t`, ваше расширение готово, но вы, возможно, захотите проверить, что оно собрано правильно.

Расширения, собранные с `abi3t`, должны иметь следующее расширение:

- В Windows: `.pyd` (как и любое другое расширение);
- Linux, macOS и другие системы, использующие суффикс `.so`: `.abi3t.so` (**не** `.cpython-315t.so` или `.abi3.so`). Обратите внимание, что как свободно-поточные, так и несвободно-поточные сборки будут загружать расширения `.abi3t.so`;
- Другие системы: обратитесь к своему дистрибьютору и, возможно, обновите это руководство.

Если вы распространяете расширение в виде *wheel*, используйте следующие теги:

- Тег Python: `cp3XX`, где *XX* – минимальная версия Python, для которой собрано расширение. (Например, `cp315`, если вы установили `Py_TARGET_ABI3T` в `0x30f0000`. См. [Компиляция для стабильного ABI](https://python-all.ru/3.16/c-api/stable.html#abi3-compiling) для других значений.)
- Тег ABI: `abi3.abi3t`. Это *сжатый набор тегов*, который указывает на поддержку как несвободно-поточных, так и свободно-поточных сборок.

Например, имя файла wheel может выглядеть так:

```text
myproject-1.0-cp315-abi3.abi3t-macosx_11_0_arm64.whl
```

> **См. также**
>
> [Теги совместимости платформ](https://python-all.ru/3.16/howto/abi3t-migration.html) в метаданных распространения пакетов PyPA.

Если имя файла или теги неверны, исправьте их.

## Тестирование

Обратите внимание: при сборке расширения, совместимого с несколькими версиями CPython, следует всегда *тестировать* его с каждой поддерживаемой версией (например, 3.15, 3.16 и так далее). Стабильный ABI гарантирует только *ABI*-совместимость; возможны также изменения поведения – как намеренные (описанные в [**PEP 387**](https://python-all.ru/3.16/howto/abi3t-migration.html)), так и ошибки.

Обязательно запускайте тесты как на свободно-поточной, так и на несвободно-поточной сборках CPython.

Если они проходят, поздравляем! У вас есть расширение `abi3t`.
