Документация Python неофициальный перевод

annotations.md

116 строк · 18.9 КБ · обычная страница · сырой текст · скачать

1> **Источник:** https://python-all.ru/3.16/howto/annotations.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# Лучшие практики работы с аннотациями89Аннотация1011Этот документ предназначен для того, чтобы собрать лучшие практики работы со словарями аннотаций. Если вы пишете код на Python, который анализирует `__annotations__` у объектов Python, мы рекомендуем следовать приведённым ниже рекомендациям.1213Документ состоит из четырёх разделов: лучшие практики доступа к аннотациям объекта в Python версии 3.10 и новее, лучшие практики доступа к аннотациям объекта в Python версии 3.9 и старше, другие лучшие практики для `__annotations__`, применимые к любой версии Python, и особенности `__annotations__`.1415Обратите внимание, что этот документ посвящён работе с `__annotations__`, а не об использовании *для* аннотаций. Если вы ищете информацию о том, как использовать «подсказки типов» в своём коде, обратитесь к модулю [`typing`](https://python-all.ru/3.16/library/typing.html#module-typing).1617## Доступ к словарю аннотаций объекта в Python 3.10 и новее1819Python 3.10 добавляет в стандартную библиотеку новую функцию: [`inspect.get_annotations()`](https://python-all.ru/3.16/library/inspect.html#inspect.get_annotations). В версиях Python с 3.10 по 3.13 вызов этой функции является лучшей практикой для доступа к словарю аннотаций любого объекта, поддерживающего аннотации. Эта функция также может преобразовывать строковые аннотации обратно в исходный вид.2021В 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).2223Если по какой-то причине [`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__` для получения словаря аннотаций объекта.2425Однако другие типы вызываемых объектов – например, созданные с помощью [`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)`.2627До Python 3.10 доступ к `__annotations__` у класса, который не определяет аннотации, но имеет родительский класс с аннотациями, возвращал `__annotations__` родителя. В Python 3.10 и новее аннотации дочернего класса вместо этого будут пустым словарём.2829## Доступ к словарю аннотаций объекта в Python 3.9 и старше3031В Python 3.9 и старше доступ к словарю аннотаций объекта намного сложнее, чем в более новых версиях. Проблема заключается в ошибке проектирования в этих старых версиях Python, связанной с аннотациями классов.3233Лучшая практика для доступа к словарю аннотаций других объектов – функций, других вызываемых объектов и модулей – такая же, как для 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__` объекта.3435К сожалению, это не лучшая практика для классов. Проблема в том, что, поскольку `__annotations__` является необязательным для классов и классы могут наследовать атрибуты от базовых классов, доступ к атрибуту `__annotations__` класса может непреднамеренно вернуть словарь аннотаций *базового класса.* Например:3637```python38class Base:39    a: int = 340    b: str = 'abc'4142class Derived(Base):43    pass4445print(Derived.__annotations__)46```4748Это выведет словарь аннотаций из `Base`, а не из `Derived`.4950Вашему коду потребуется отдельный путь обработки, если исследуемый объект является классом (`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) на словаре класса.5152Собирая всё вместе, вот пример кода, который безопасно обращается к атрибуту `__annotations__` у произвольного объекта в Python 3.9 и более ранних версиях:5354```python55if isinstance(o, type):56    ann = o.__dict__.get('__annotations__', None)57else:58    ann = getattr(o, '__annotations__', None)59```6061После выполнения этого кода `ann` должен быть либо словарём, либо `None`. Рекомендуется перепроверить тип `ann` с помощью [`isinstance()`](https://python-all.ru/3.16/library/functions.html#isinstance) перед дальнейшим изучением.6263Обратите внимание, что некоторые экзотические или некорректно сформированные объекты типов могут не иметь атрибута [`__dict__`](https://python-all.ru/3.16/reference/datamodel.html#type.__dict__), поэтому для дополнительной безопасности можно также использовать [`getattr()`](https://python-all.ru/3.16/library/functions.html#getattr) для доступа к `__dict__`.6465## Ручное обратное преобразование строковых аннотаций6667В ситуациях, когда некоторые аннотации могут быть «строкизированы» и требуется вычислить эти строки, чтобы получить представляемые ими значения Python, лучше всего вызвать [`inspect.get_annotations()`](https://python-all.ru/3.16/library/inspect.html#inspect.get_annotations) для выполнения этой работы.6869Если используется Python 3.9 или старше, или по какой-то причине нельзя использовать [`inspect.get_annotations()`](https://python-all.ru/3.16/library/inspect.html#inspect.get_annotations), потребуется воспроизвести его логику. Рекомендуется изучить реализацию `inspect.get_annotations()` в текущей версии Python и применить аналогичный подход.7071Если кратко, чтобы вычислить строкизированную аннотацию на произвольном объекте `o`:7273- Если `o` – модуль, `o.__dict__` следует передать как `globals` при вызове [`eval()`](https://python-all.ru/3.16/library/functions.html#eval).74- Если `o` – класс, `sys.modules[o.__module__].__dict__` следует передать как `globals`, а `dict(vars(o))` как `locals` при вызове [`eval()`](https://python-all.ru/3.16/library/functions.html#eval).75- Если `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` по необходимости, пока не будет найдена корневая необёрнутая функция.76- Если `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).7778Однако не все строковые значения, используемые в качестве аннотаций, могут быть успешно преобразованы в значения Python с помощью [`eval()`](https://python-all.ru/3.16/library/functions.html#eval). Строковые значения теоретически могут содержать любую допустимую строку, и на практике существуют обоснованные сценарии использования подсказок типов, требующие аннотирования строковыми значениями, которые *не могут быть* вычислены. Например:7980- [**PEP 604** типы объединений с использованием](https://python-all.ru/3.16/howto/annotations.html), до того как поддержка этого была добавлена в Python 3.10.81- Определения, не требующиеся во время выполнения, импортируемые только когда [`typing.TYPE_CHECKING`](https://python-all.ru/3.16/library/typing.html#typing.TYPE_CHECKING) истинно.8283Если [`eval()`](https://python-all.ru/3.16/library/functions.html#eval) попытается вычислить такие значения, произойдёт ошибка и будет возбуждено исключение. Поэтому при разработке библиотечного API, работающего с аннотациями, рекомендуется пытаться вычислять строковые значения только по явному запросу вызывающей стороны.8485## Рекомендации по `__annotations__` в любой версии Python8687- Следует избегать прямого присваивания атрибуту `__annotations__` объектов. Управление установкой `__annotations__` следует оставить Python.88- Если всё же выполняется прямое присваивание атрибуту `__annotations__` объекта, всегда следует устанавливать его равным объекту `dict`.89- Следует избегать прямого доступа к `__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+).90- Если всё же выполняется прямой доступ к атрибуту `__annotations__` объекта, следует убедиться, что это словарь, прежде чем пытаться исследовать его содержимое.91- Следует избегать изменения словарей `__annotations__`.92- Следует избегать удаления атрибута `__annotations__` объекта.9394## `__annotations__` Особенности9596Во всех версиях Python 3 объекты-функции лениво создают словарь аннотаций, если для этого объекта аннотации не определены. Можно удалить атрибут `__annotations__` с помощью `del fn.__annotations__`, но если затем обратиться к `fn.__annotations__`, объект создаст новый пустой словарь, который будет сохранён и возвращён как его аннотации. Удаление аннотаций у функции до того, как она лениво создала свой словарь аннотаций, вызовет исключение `AttributeError`; двукратное последовательное использование `del fn.__annotations__` гарантированно всегда вызовет `AttributeError`.9798Всё, сказанное в предыдущем абзаце, также относится к объектам классов и модулей в Python 3.10 и новее.99100Во всех версиях Python 3 значение `__annotations__` у объекта-функции может быть установлено в `None`. Однако последующий доступ к аннотациям этого объекта с помощью `fn.__annotations__` приведёт к ленивому созданию пустого словаря, как описано в первом абзаце этого раздела. Это *не* относится к модулям и классам в любой версии Python; эти объекты позволяют устанавливать `__annotations__` в любое значение Python и будут сохранять установленное значение.101102Если Python сам строкизирует аннотации (с помощью `from __future__ import annotations`), и в качестве аннотации указана строка, эта строка будет заключена в кавычки. По сути, аннотация оказывается заключённой в кавычки *дважды.* Например:103104```python105from __future__ import annotations106def foo(a: "str"): pass107108print(foo.__annotations__)109```110111Это выводит `{'a': "'str'"}`. Это не стоит считать «странностью»; это упоминается здесь просто потому, что может показаться неожиданным.112113Если используется класс с пользовательским метаклассом и выполняется доступ к `__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)`).114115В некоторых версиях Python экземпляры классов могут иметь атрибут `__annotations__`. Однако это не поддерживаемая функциональность. Если нужны аннотации экземпляра, можно использовать [`type()`](https://python-all.ru/3.16/library/functions.html#type) для доступа к его классу (например, `annotationlib.get_annotations(type(myinstance))` в Python 3.14+).116