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

annotations.md

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

1> **Источник:** https://python-all.ru/3.12/howto/annotations.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# Лучшие практики работы с аннотациями89**автор:**1011Larry Hastings1213Аннотация1415Этот документ предназначен для того, чтобы собрать лучшие практики работы со словарями аннотаций. Если вы пишете код на Python, который анализирует `__annotations__` у объектов Python, мы рекомендуем следовать приведённым ниже рекомендациям.1617Документ состоит из четырёх разделов: лучшие практики доступа к аннотациям объекта в Python версии 3.10 и новее, лучшие практики доступа к аннотациям объекта в Python версии 3.9 и старше, другие лучшие практики для `__annotations__`, применимые к любой версии Python, и особенности `__annotations__`.1819Обратите внимание, что этот документ посвящён работе с `__annotations__`, а не об использовании *для* аннотаций. Если вы ищете информацию о том, как использовать «подсказки типов» в своём коде, обратитесь к модулю [`typing`](https://python-all.ru/3.12/library/typing.html#module-typing).2021## Доступ к словарю аннотаций объекта в Python 3.10 и новее2223Python 3.10 добавляет в стандартную библиотеку новую функцию: [`inspect.get_annotations()`](https://python-all.ru/3.12/library/inspect.html#inspect.get_annotations). В Python версии 3.10 и новее вызов этой функции является рекомендуемым способом для доступа к словарю аннотаций любого объекта, поддерживающего аннотации. Эта функция также может «расквочивать» строковые аннотации для вас.2425Если по какой-то причине [`inspect.get_annotations()`](https://python-all.ru/3.12/library/inspect.html#inspect.get_annotations) не подходит для вашего случая, вы можете получить доступ к элементу данных `__annotations__` вручную. Лучшая практика для этого также изменилась в Python 3.10: начиная с Python 3.10, `o.__annotations__` гарантированно *всегда* работает с функциями, классами и модулями Python. Если вы уверены, что исследуемый объект является одним из этих трёх *конкретных* объектов, вы можете просто использовать `o.__annotations__` для получения словаря аннотаций объекта.2627Однако другие типы вызываемых объектов – например, созданные с помощью [`functools.partial()`](https://python-all.ru/3.12/library/functools.html#functools.partial) – могут не иметь атрибута `__annotations__`. При доступе к `__annotations__` возможно неизвестного объекта лучшая практика в Python версиях 3.10 и новее – вызывать [`getattr()`](https://python-all.ru/3.12/library/functions.html#getattr) с тремя аргументами, например `getattr(o, '__annotations__', None)`.2829До Python 3.10 доступ к `__annotations__` у класса, который не определяет аннотации, но имеет родительский класс с аннотациями, возвращал `__annotations__` родителя. В Python 3.10 и новее аннотации дочернего класса вместо этого будут пустым словарём.3031## Доступ к словарю аннотаций объекта в Python 3.9 и старше3233В Python 3.9 и старше доступ к словарю аннотаций объекта намного сложнее, чем в более новых версиях. Проблема заключается в ошибке проектирования в этих старых версиях Python, связанной с аннотациями классов.3435Лучшая практика для доступа к словарю аннотаций других объектов – функций, других вызываемых объектов и модулей – такая же, как для 3.10, при условии, что вы не вызываете [`inspect.get_annotations()`](https://python-all.ru/3.12/library/inspect.html#inspect.get_annotations): следует использовать [`getattr()`](https://python-all.ru/3.12/library/functions.html#getattr) с тремя аргументами для доступа к атрибуту `__annotations__` объекта.3637К сожалению, это не лучшая практика для классов. Проблема в том, что, поскольку `__annotations__` является необязательным для классов и классы могут наследовать атрибуты от базовых классов, доступ к атрибуту `__annotations__` класса может непреднамеренно вернуть словарь аннотаций *базового класса.* Например:3839```python40class Base:41    a: int = 342    b: str = 'abc'4344class Derived(Base):45    pass4647print(Derived.__annotations__)48```4950Это выведет словарь аннотаций из `Base`, а не из `Derived`.5152Вашему коду потребуется отдельный путь обработки, если исследуемый объект является классом (`isinstance(o, type)`). В этом случае лучшая практика опирается на деталь реализации Python 3.9 и более ранних версий: если у класса определены аннотации, они хранятся в словаре [`__dict__`](https://python-all.ru/3.12/reference/datamodel.html#type.__dict__) класса. Поскольку аннотации могут быть определены, а могут и не быть, лучшей практикой является вызов метода [`get()`](https://python-all.ru/3.12/library/stdtypes.html#dict.get) на словаре класса.5354Собирая всё вместе, вот пример кода, который безопасно обращается к атрибуту `__annotations__` у произвольного объекта в Python 3.9 и более ранних версиях:5556```python57if isinstance(o, type):58    ann = o.__dict__.get('__annotations__', None)59else:60    ann = getattr(o, '__annotations__', None)61```6263После выполнения этого кода `ann` должен быть либо словарём, либо `None`. Рекомендуется перепроверить тип `ann` с помощью [`isinstance()`](https://python-all.ru/3.12/library/functions.html#isinstance) перед дальнейшим изучением.6465Обратите внимание, что некоторые экзотические или некорректно сформированные объекты типов могут не иметь атрибута [`__dict__`](https://python-all.ru/3.12/reference/datamodel.html#type.__dict__), поэтому для дополнительной безопасности можно также использовать [`getattr()`](https://python-all.ru/3.12/library/functions.html#getattr) для доступа к `__dict__`.6667## Ручное обратное преобразование строковых аннотаций6869В ситуациях, когда некоторые аннотации могут быть «строкизированы» и требуется вычислить эти строки, чтобы получить представляемые ими значения Python, лучше всего вызвать [`inspect.get_annotations()`](https://python-all.ru/3.12/library/inspect.html#inspect.get_annotations) для выполнения этой работы.7071Если используется Python 3.9 или старше, или по какой-то причине нельзя использовать [`inspect.get_annotations()`](https://python-all.ru/3.12/library/inspect.html#inspect.get_annotations), потребуется воспроизвести его логику. Рекомендуется изучить реализацию [`inspect.get_annotations()`](https://python-all.ru/3.12/library/inspect.html#inspect.get_annotations) в текущей версии Python и применить аналогичный подход.7273Если кратко, чтобы вычислить строкизированную аннотацию на произвольном объекте `o`:7475- Если `o` – модуль, `o.__dict__` следует передать как `globals` при вызове [`eval()`](https://python-all.ru/3.12/library/functions.html#eval).76- Если `o` – класс, `sys.modules[o.__module__].__dict__` следует передать как `globals`, а `dict(vars(o))` как `locals` при вызове [`eval()`](https://python-all.ru/3.12/library/functions.html#eval).77- Если `o` – обёрнутый вызываемый объект с использованием [`functools.update_wrapper()`](https://python-all.ru/3.12/library/functools.html#functools.update_wrapper), [`functools.wraps()`](https://python-all.ru/3.12/library/functools.html#functools.wraps) или [`functools.partial()`](https://python-all.ru/3.12/library/functools.html#functools.partial), его необходимо последовательно развернуть, обращаясь к `o.__wrapped__` или `o.func` по необходимости, пока не будет найдена корневая необёрнутая функция.78- Если `o` – вызываемый объект (но не класс), [`o.__globals__`](https://python-all.ru/3.12/reference/datamodel.html#function.__globals__) следует передать как globals при вызове [`eval()`](https://python-all.ru/3.12/library/functions.html#eval).7980Однако не все строковые значения, используемые в качестве аннотаций, могут быть успешно преобразованы в значения Python с помощью [`eval()`](https://python-all.ru/3.12/library/functions.html#eval). Строковые значения теоретически могут содержать любую допустимую строку, и на практике существуют обоснованные сценарии использования подсказок типов, требующие аннотирования строковыми значениями, которые *не могут быть* вычислены. Например:8182- [**PEP 604** типы объединений с использованием](https://python-all.ru/3.12/howto/annotations.html), до того как поддержка этого была добавлена в Python 3.10.83- Определения, не требующиеся во время выполнения, импортируемые только когда [`typing.TYPE_CHECKING`](https://python-all.ru/3.12/library/typing.html#typing.TYPE_CHECKING) истинно.8485Если [`eval()`](https://python-all.ru/3.12/library/functions.html#eval) попытается вычислить такие значения, произойдёт ошибка и будет возбуждено исключение. Поэтому при разработке библиотечного API, работающего с аннотациями, рекомендуется пытаться вычислять строковые значения только по явному запросу вызывающей стороны.8687## Рекомендации по `__annotations__` в любой версии Python8889- Следует избегать прямого присваивания атрибуту `__annotations__` объектов. Управление установкой `__annotations__` следует оставить Python.90- Если всё же выполняется прямое присваивание атрибуту `__annotations__` объекта, всегда следует устанавливать его равным объекту `dict`.91- При прямом доступе к атрибуту `__annotations__` объекта следует убедиться, что он является словарём, прежде чем пытаться просмотреть его содержимое.92- Следует избегать изменения словарей `__annotations__`.93- Следует избегать удаления атрибута `__annotations__` объекта.9495## `__annotations__` Особенности9697Во всех версиях Python 3 объекты-функции лениво создают словарь аннотаций, если для этого объекта аннотации не определены. Можно удалить атрибут `__annotations__` с помощью `del fn.__annotations__`, но если затем обратиться к `fn.__annotations__`, объект создаст новый пустой словарь, который будет сохранён и возвращён как его аннотации. Удаление аннотаций у функции до того, как она лениво создала свой словарь аннотаций, вызовет исключение `AttributeError`; двукратное последовательное использование `del fn.__annotations__` гарантированно всегда вызовет `AttributeError`.9899Всё, сказанное в предыдущем абзаце, также относится к объектам классов и модулей в Python 3.10 и новее.100101Во всех версиях Python 3 значение `__annotations__` у объекта-функции может быть установлено в `None`. Однако последующий доступ к аннотациям этого объекта с помощью `fn.__annotations__` приведёт к ленивому созданию пустого словаря, как описано в первом абзаце этого раздела. Это *не* относится к модулям и классам в любой версии Python; эти объекты позволяют устанавливать `__annotations__` в любое значение Python и будут сохранять установленное значение.102103Если Python сам строкизирует аннотации (с помощью `from __future__ import annotations`), и в качестве аннотации указана строка, эта строка будет заключена в кавычки. По сути, аннотация оказывается заключённой в кавычки *дважды.* Например:104105```python106from __future__ import annotations107def foo(a: "str"): pass108109print(foo.__annotations__)110```111112Это выводит `{'a': "'str'"}`. Это не стоит считать «странностью»; это упоминается здесь просто потому, что может показаться неожиданным.113