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

---

# Лучшие практики работы с аннотациями

Аннотация

Этот документ предназначен для того, чтобы собрать лучшие практики работы со словарями аннотаций. Если вы пишете код на Python, который анализирует `__annotations__` у объектов Python, мы рекомендуем следовать приведённым ниже рекомендациям.

Документ состоит из четырёх разделов: лучшие практики доступа к аннотациям объекта в Python версии 3.10 и новее, лучшие практики доступа к аннотациям объекта в Python версии 3.9 и старше, другие лучшие практики для `__annotations__`, применимые к любой версии Python, и особенности `__annotations__`.

Обратите внимание, что этот документ посвящён работе с `__annotations__`, а не об использовании *для* аннотаций. Если вы ищете информацию о том, как использовать «подсказки типов» в своём коде, обратитесь к модулю [`typing`](https://python-all.ru/3.16/library/typing.html#module-typing).

## Доступ к словарю аннотаций объекта в Python 3.10 и новее

Python 3.10 добавляет в стандартную библиотеку новую функцию: [`inspect.get_annotations()`](https://python-all.ru/3.16/library/inspect.html#inspect.get_annotations). В версиях Python с 3.10 по 3.13 вызов этой функции является лучшей практикой для доступа к словарю аннотаций любого объекта, поддерживающего аннотации. Эта функция также может преобразовывать строковые аннотации обратно в исходный вид.

В Python 3.14 появился новый модуль [`annotationlib`](https://python-all.ru/3.16/library/annotationlib.html#module-annotationlib) с функциональностью для работы с аннотациями. Он включает функцию [`annotationlib.get_annotations()`](https://python-all.ru/3.16/library/annotationlib.html#annotationlib.get_annotations), которая заменяет [`inspect.get_annotations()`](https://python-all.ru/3.16/library/inspect.html#inspect.get_annotations).

Если по какой-то причине [`inspect.get_annotations()`](https://python-all.ru/3.16/library/inspect.html#inspect.get_annotations) не подходит для вашего случая, вы можете получить доступ к элементу данных `__annotations__` вручную. Лучшая практика для этого также изменилась в Python 3.10: начиная с Python 3.10, `o.__annotations__` гарантированно *всегда* работает с функциями, классами и модулями Python. Если вы уверены, что исследуемый объект является одним из этих трёх *конкретных* объектов, вы можете просто использовать `o.__annotations__` для получения словаря аннотаций объекта.

Однако другие типы вызываемых объектов – например, созданные с помощью [`functools.partial()`](https://python-all.ru/3.16/library/functools.html#functools.partial) – могут не иметь атрибута `__annotations__`. При доступе к `__annotations__` возможно неизвестного объекта лучшая практика в Python версиях 3.10 и новее – вызывать [`getattr()`](https://python-all.ru/3.16/library/functions.html#getattr) с тремя аргументами, например `getattr(o, '__annotations__', None)`.

До Python 3.10 доступ к `__annotations__` у класса, который не определяет аннотации, но имеет родительский класс с аннотациями, возвращал `__annotations__` родителя. В Python 3.10 и новее аннотации дочернего класса вместо этого будут пустым словарём.

## Доступ к словарю аннотаций объекта в Python 3.9 и старше

В Python 3.9 и старше доступ к словарю аннотаций объекта намного сложнее, чем в более новых версиях. Проблема заключается в ошибке проектирования в этих старых версиях Python, связанной с аннотациями классов.

Лучшая практика для доступа к словарю аннотаций других объектов – функций, других вызываемых объектов и модулей – такая же, как для 3.10, при условии, что вы не вызываете [`inspect.get_annotations()`](https://python-all.ru/3.16/library/inspect.html#inspect.get_annotations): следует использовать [`getattr()`](https://python-all.ru/3.16/library/functions.html#getattr) с тремя аргументами для доступа к атрибуту `__annotations__` объекта.

К сожалению, это не лучшая практика для классов. Проблема в том, что, поскольку `__annotations__` является необязательным для классов и классы могут наследовать атрибуты от базовых классов, доступ к атрибуту `__annotations__` класса может непреднамеренно вернуть словарь аннотаций *базового класса.* Например:

```python
class Base:
    a: int = 3
    b: str = 'abc'

class Derived(Base):
    pass

print(Derived.__annotations__)
```

Это выведет словарь аннотаций из `Base`, а не из `Derived`.

Вашему коду потребуется отдельный путь обработки, если исследуемый объект является классом (`isinstance(o, type)`). В этом случае лучшая практика опирается на деталь реализации Python 3.9 и более ранних версий: если у класса определены аннотации, они хранятся в словаре [`__dict__`](https://python-all.ru/3.16/reference/datamodel.html#type.__dict__) класса. Поскольку аннотации могут быть определены, а могут и не быть, лучшей практикой является вызов метода [`get()`](https://python-all.ru/3.16/library/stdtypes.html#dict.get) на словаре класса.

Собирая всё вместе, вот пример кода, который безопасно обращается к атрибуту `__annotations__` у произвольного объекта в Python 3.9 и более ранних версиях:

```python
if isinstance(o, type):
    ann = o.__dict__.get('__annotations__', None)
else:
    ann = getattr(o, '__annotations__', None)
```

После выполнения этого кода `ann` должен быть либо словарём, либо `None`. Рекомендуется перепроверить тип `ann` с помощью [`isinstance()`](https://python-all.ru/3.16/library/functions.html#isinstance) перед дальнейшим изучением.

Обратите внимание, что некоторые экзотические или некорректно сформированные объекты типов могут не иметь атрибута [`__dict__`](https://python-all.ru/3.16/reference/datamodel.html#type.__dict__), поэтому для дополнительной безопасности можно также использовать [`getattr()`](https://python-all.ru/3.16/library/functions.html#getattr) для доступа к `__dict__`.

## Ручное обратное преобразование строковых аннотаций

В ситуациях, когда некоторые аннотации могут быть «строкизированы» и требуется вычислить эти строки, чтобы получить представляемые ими значения Python, лучше всего вызвать [`inspect.get_annotations()`](https://python-all.ru/3.16/library/inspect.html#inspect.get_annotations) для выполнения этой работы.

Если используется Python 3.9 или старше, или по какой-то причине нельзя использовать [`inspect.get_annotations()`](https://python-all.ru/3.16/library/inspect.html#inspect.get_annotations), потребуется воспроизвести его логику. Рекомендуется изучить реализацию `inspect.get_annotations()` в текущей версии Python и применить аналогичный подход.

Если кратко, чтобы вычислить строкизированную аннотацию на произвольном объекте `o`:

- Если `o` – модуль, `o.__dict__` следует передать как `globals` при вызове [`eval()`](https://python-all.ru/3.16/library/functions.html#eval).
- Если `o` – класс, `sys.modules[o.__module__].__dict__` следует передать как `globals`, а `dict(vars(o))` как `locals` при вызове [`eval()`](https://python-all.ru/3.16/library/functions.html#eval).
- Если `o` – обёрнутый вызываемый объект с использованием [`functools.update_wrapper()`](https://python-all.ru/3.16/library/functools.html#functools.update_wrapper), [`@functools.wraps`](https://python-all.ru/3.16/library/functools.html#functools.wraps) или [`functools.partial()`](https://python-all.ru/3.16/library/functools.html#functools.partial), его необходимо последовательно развернуть, обращаясь к `o.__wrapped__` или `o.func` по необходимости, пока не будет найдена корневая необёрнутая функция.
- Если `o` – вызываемый объект (но не класс), [`o.__globals__`](https://python-all.ru/3.16/reference/datamodel.html#function.__globals__) следует передать как globals при вызове [`eval()`](https://python-all.ru/3.16/library/functions.html#eval).

Однако не все строковые значения, используемые в качестве аннотаций, могут быть успешно преобразованы в значения Python с помощью [`eval()`](https://python-all.ru/3.16/library/functions.html#eval). Строковые значения теоретически могут содержать любую допустимую строку, и на практике существуют обоснованные сценарии использования подсказок типов, требующие аннотирования строковыми значениями, которые *не могут быть* вычислены. Например:

- [**PEP 604** типы объединений с использованием](https://python-all.ru/3.16/howto/annotations.html), до того как поддержка этого была добавлена в Python 3.10.
- Определения, не требующиеся во время выполнения, импортируемые только когда [`typing.TYPE_CHECKING`](https://python-all.ru/3.16/library/typing.html#typing.TYPE_CHECKING) истинно.

Если [`eval()`](https://python-all.ru/3.16/library/functions.html#eval) попытается вычислить такие значения, произойдёт ошибка и будет возбуждено исключение. Поэтому при разработке библиотечного API, работающего с аннотациями, рекомендуется пытаться вычислять строковые значения только по явному запросу вызывающей стороны.

## Рекомендации по `__annotations__` в любой версии Python

- Следует избегать прямого присваивания атрибуту `__annotations__` объектов. Управление установкой `__annotations__` следует оставить Python.
- Если всё же выполняется прямое присваивание атрибуту `__annotations__` объекта, всегда следует устанавливать его равным объекту `dict`.
- Следует избегать прямого доступа к `__annotations__` у любого объекта. Вместо этого следует использовать [`annotationlib.get_annotations()`](https://python-all.ru/3.16/library/annotationlib.html#annotationlib.get_annotations) (Python 3.14+) или [`inspect.get_annotations()`](https://python-all.ru/3.16/library/inspect.html#inspect.get_annotations) (Python 3.10+).
- Если всё же выполняется прямой доступ к атрибуту `__annotations__` объекта, следует убедиться, что это словарь, прежде чем пытаться исследовать его содержимое.
- Следует избегать изменения словарей `__annotations__`.
- Следует избегать удаления атрибута `__annotations__` объекта.

## `__annotations__` Особенности

Во всех версиях Python 3 объекты-функции лениво создают словарь аннотаций, если для этого объекта аннотации не определены. Можно удалить атрибут `__annotations__` с помощью `del fn.__annotations__`, но если затем обратиться к `fn.__annotations__`, объект создаст новый пустой словарь, который будет сохранён и возвращён как его аннотации. Удаление аннотаций у функции до того, как она лениво создала свой словарь аннотаций, вызовет исключение `AttributeError`; двукратное последовательное использование `del fn.__annotations__` гарантированно всегда вызовет `AttributeError`.

Всё, сказанное в предыдущем абзаце, также относится к объектам классов и модулей в Python 3.10 и новее.

Во всех версиях Python 3 значение `__annotations__` у объекта-функции может быть установлено в `None`. Однако последующий доступ к аннотациям этого объекта с помощью `fn.__annotations__` приведёт к ленивому созданию пустого словаря, как описано в первом абзаце этого раздела. Это *не* относится к модулям и классам в любой версии Python; эти объекты позволяют устанавливать `__annotations__` в любое значение Python и будут сохранять установленное значение.

Если Python сам строкизирует аннотации (с помощью `from __future__ import annotations`), и в качестве аннотации указана строка, эта строка будет заключена в кавычки. По сути, аннотация оказывается заключённой в кавычки *дважды.* Например:

```python
from __future__ import annotations
def foo(a: "str"): pass

print(foo.__annotations__)
```

Это выводит `{'a': "'str'"}`. Это не стоит считать «странностью»; это упоминается здесь просто потому, что может показаться неожиданным.

Если используется класс с пользовательским метаклассом и выполняется доступ к `__annotations__` у этого класса, может наблюдаться неожиданное поведение; см. [**749**](https://python-all.ru/3.16/howto/annotations.html) для примеров. Этих особенностей можно избежать, используя [`annotationlib.get_annotations()`](https://python-all.ru/3.16/library/annotationlib.html#annotationlib.get_annotations) в Python 3.14+ или [`inspect.get_annotations()`](https://python-all.ru/3.16/library/inspect.html#inspect.get_annotations) в Python 3.10+. В более старых версиях Python этих ошибок можно избежать, обращаясь к аннотациям через [`__dict__`](https://python-all.ru/3.16/reference/datamodel.html#type.__dict__) класса (например, `cls.__dict__.get('__annotations__', None)`).

В некоторых версиях Python экземпляры классов могут иметь атрибут `__annotations__`. Однако это не поддерживаемая функциональность. Если нужны аннотации экземпляра, можно использовать [`type()`](https://python-all.ru/3.16/library/functions.html#type) для доступа к его классу (например, `annotationlib.get_annotations(type(myinstance))` в Python 3.14+).
