Spec-Zone.ru › Python 3.14

annotationlib — Функции для интроспекции аннотаций

Добавлено в версии 3.14.

Исходный код: Lib/annotationlib.py

Модуль annotationlib предоставляет инструменты для интроспекции аннотаций модулей, классов и функций.

Аннотации вычисляются отложенно и часто содержат прямые ссылки на объекты, которые ещё не определены на момент создания аннотации. Этот модуль предоставляет набор низкоуровневых инструментов, с помощью которых можно надёжно получать аннотации, даже если они содержат прямые ссылки или другие сложные случаи.

Модуль поддерживает получение аннотаций в трёх основных форматах (см. Format), каждый из которых лучше подходит для разных случаев использования:

  • VALUE вычисляет аннотации и возвращает их значения. С этим форматом проще всего работать, но он может вызывать ошибки, например если аннотации содержат ссылки на неопределённые имена.
  • FORWARDREF возвращает объекты ForwardRef для аннотаций, которые не удаётся разрешить, что позволяет проверять аннотации, не вычисляя их. Этот формат полезен, когда нужно работать с аннотациями, которые могут содержать неразрешённые прямые ссылки.
  • STRING возвращает аннотации в виде строки, похожей на ту, что была бы в исходном файле. Этот формат полезен генераторам документации, которым нужно показывать аннотации в удобочитаемом виде.

Функция get_annotations() — основная точка входа для получения аннотаций. Она принимает функцию, класс или модуль и возвращает словарь аннотаций в запрошенном формате. Этот модуль также предоставляет функции для непосредственной работы с функцией annotate, используемой для вычисления аннотаций, например get_annotate_from_class_namespace() и call_annotate_function(), а также функцию call_evaluate_function() для работы с функциями evaluate.

Предупреждение

Большинство функций этого модуля могут выполнять произвольный код; дополнительную информацию см. в разделе о безопасности.

См. также

PEP 649 предложил текущую модель работы аннотаций в Python.

PEP 749 развил различные аспекты PEP 649 и представил модуль annotationlib.

Рекомендации по работе с аннотациями содержит рекомендации по работе с аннотациями.

typing-extensions предоставляет обратный порт get_annotations(), который работает в более ранних версиях Python.

Семантика аннотаций

Способ вычисления аннотаций менялся на протяжении истории Python 3 и в настоящее время всё ещё зависит от импорта future. Использовались следующие модели выполнения аннотаций:

  • Стандартная семантика (по умолчанию в Python 3.0–3.13; см. PEP 3107 и PEP 526): аннотации вычисляются сразу при встрече в исходном коде.
  • Строковые аннотации (используются с from __future__ import annotations в Python 3.7 и новее; см. PEP 563): аннотации сохраняются только в виде строк.
  • Отложенное вычисление (по умолчанию в Python 3.14 и новее; см. PEP 649 и PEP 749): аннотации вычисляются отложенно, только при обращении к ним.

Рассмотрим в качестве примера следующую программу:

def func(a: Cls) -> None:
    print(a)

class Cls: pass

print(func.__annotations__)

Она будет работать следующим образом:

  • При стандартной семантике (Python 3.13 и более ранние версии) на строке, где определена func, возникнет ошибка NameError, поскольку на тот момент Cls — неопределённое имя.
  • При использовании строковых аннотаций (если используется from __future__ import annotations) программа выведет {'a': 'Cls', 'return': 'None'}.
  • При отложенном вычислении (Python 3.14 и новее) программа выведет {'a': <class 'Cls'>, 'return': None}.

Стандартная семантика использовалась с момента появления аннотаций функций в Python 3.0 (в соответствии с PEP 3107), поскольку это был самый простой и очевидный способ реализовать аннотации. Та же модель выполнения использовалась при добавлении аннотаций переменных в Python 3.6 (в соответствии с PEP 526). Однако стандартная семантика создавала проблемы при использовании аннотаций в качестве подсказок типов, например необходимость ссылаться на имена, которые ещё не определены в момент встречи аннотации. Кроме того, возникали проблемы с производительностью из-за выполнения аннотаций во время импорта модуля. Поэтому в Python 3.7 в PEP 563 была введена возможность сохранять аннотации в виде строк с помощью синтаксиса from __future__ import annotations. Тогда планировалось со временем сделать такое поведение стандартным, но обнаружилась проблема: интроспекция строковых аннотаций во время выполнения затруднена. Альтернативное предложение — PEP 649 — представило третью модель выполнения, отложенное вычисление, которая была реализована в Python 3.14. Строковые аннотации по-прежнему используются, если присутствует from __future__ import annotations, но со временем это поведение будет удалено.

Классы

class annotationlib.Format

Перечисление IntEnum, описывающее форматы, в которых могут возвращаться аннотации. Элементы этого перечисления или соответствующие им целочисленные значения можно передавать функции get_annotations() и другим функциям этого модуля, а также функциям __annotate__.

VALUE = 1

Значения — это результат вычисления выражений аннотаций.

VALUE_WITH_FAKE_GLOBALS = 2

Специальное значение, указывающее, что функция annotate вычисляется в особом окружении с фиктивными глобальными переменными. Получив это значение, функции annotate должны либо вернуть то же значение, что и для формата Format.VALUE, либо вызвать исключение NotImplementedError, показывая тем самым, что они не поддерживают выполнение в этом окружении. Этот формат используется только внутри модуля и не должен передаваться функциям этого модуля.

FORWARDREF = 3

Для определённых значений возвращаются реальные значения аннотаций (согласно формату Format.VALUE), а для неопределённых — прокси ForwardRef. Реальные объекты могут содержать ссылки на прокси-объекты ForwardRef.

STRING = 4

Значениями являются текстовые строки аннотаций в том виде, в каком они представлены в исходном коде, с возможными изменениями, включая, помимо прочего, нормализацию пробелов и оптимизацию константных значений.

Точные значения этих строк могут измениться в будущих версиях Python.

Добавлено в версии 3.14.

class annotationlib.ForwardRef

Прокси-объект для прямых ссылок в аннотациях.

Экземпляры этого класса возвращаются при использовании формата FORWARDREF, если аннотации содержат имя, которое не удаётся разрешить. Это может произойти, когда в аннотации используется прямая ссылка, например если ссылка ведёт на класс, который ещё не определён.

__forward_arg__

Строка, содержащая код, который был вычислен для создания ForwardRef. Эта строка может не полностью совпадать с исходным кодом.

evaluate(*, owner=None, globals=None, locals=None, type_params=None, format=Format.VALUE)

Вычисляет прямую ссылку и возвращает её значение.

Если аргумент format равен VALUE (значение по умолчанию), этот метод может вызвать исключение, например NameError, если прямая ссылка указывает на имя, которое не удаётся разрешить. Аргументы этого метода можно использовать, чтобы задать привязки для имён, которые иначе оставались бы неопределёнными. Если аргумент format равен FORWARDREF, метод никогда не вызовет исключение, но может вернуть экземпляр ForwardRef. Например, если объект прямой ссылки содержит код list[undefined], где undefined — неопределённое имя, его вычисление с форматом FORWARDREF вернёт list[ForwardRef('undefined')]. Если аргумент format равен STRING, метод вернёт __forward_arg__.

Параметр owner — предпочтительный способ передать этому методу информацию об области видимости. Владелец объекта ForwardRef — это объект, содержащий аннотацию, из которой происходит ForwardRef, например объект модуля, типа или функции.

Параметры globals, locals и type_params позволяют точнее управлять именами, доступными при вычислении ForwardRef. Параметры globals и locals передаются функции eval() и представляют глобальное и локальное пространства имён, в которых вычисляется имя. Параметр type_params важен для объектов, созданных с использованием встроенного синтаксиса для обобщённых классов и функций. Это кортеж параметров типа, находящихся в области видимости при вычислении прямой ссылки. Например, при вычислении ForwardRef, полученного из аннотации в пространстве имён класса обобщённого класса C, параметру type_params следует присвоить значение C.__type_params__.

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

Если не указаны параметры owner, globals, locals или type_params, а ForwardRef не содержит информации о своём происхождении, используются пустые словари globals и locals.

Добавлено в версии 3.14.

Функции

annotationlib.annotations_to_string(annotations)

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

Например, она используется для реализации формата STRING для классов typing.TypedDict, созданных с помощью функционального синтаксиса:

>>> from typing import TypedDict
>>> Movie = TypedDict("movie", {"name": str, "year": int})
>>> get_annotations(Movie, format=Format.STRING)
{'name': 'str', 'year': 'int'}

Добавлено в версии 3.14.

annotationlib.call_annotate_function(annotate, format, *, owner=None)

Вызывает функцию annotate annotate с заданным параметром format, являющимся членом перечисления Format, и возвращает словарь аннотаций, созданный функцией.

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

owner — объект, которому принадлежит функция аннотаций; обычно это функция, класс или модуль. Если этот параметр задан, в формате FORWARDREF он используется для создания объекта ForwardRef, содержащего дополнительную информацию.

См. также

PEP 649 содержит объяснение метода реализации, используемого этой функцией.

Добавлено в версии 3.14.

annotationlib.call_evaluate_function(evaluate, format, *, owner=None)

Вызывает функцию evaluate evaluate с заданным параметром format, являющимся членом перечисления Format, и возвращает значение, полученное от функции. Это похоже на call_annotate_function(), однако последняя всегда возвращает словарь, сопоставляющий строки с аннотациями, а эта функция возвращает одно значение.

Эта функция предназначена для использования с функциями evaluate, создаваемыми для лениво вычисляемых элементов, связанных с псевдонимами типов и параметрами типов:

  • typing.TypeAliasType.evaluate_value(), значение псевдонимов типов
  • typing.TypeVar.evaluate_bound(), ограничение переменных типов
  • typing.TypeVar.evaluate_constraints(), ограничения переменных типов
  • typing.TypeVar.evaluate_default(), значение по умолчанию переменных типов
  • typing.ParamSpec.evaluate_default(), значение по умолчанию спецификаций параметров
  • typing.TypeVarTuple.evaluate_default(), значение по умолчанию кортежей переменных типов

owner — объект, которому принадлежит функция evaluate, например псевдоним типа или объект переменной типа.

Параметр format позволяет задать формат возвращаемого значения:

>>> type Alias = undefined
>>> call_evaluate_function(Alias.evaluate_value, Format.VALUE)
Traceback (most recent call last):
...
NameError: name 'undefined' is not defined
>>> call_evaluate_function(Alias.evaluate_value, Format.FORWARDREF)
ForwardRef('undefined')
>>> call_evaluate_function(Alias.evaluate_value, Format.STRING)
'undefined'

Добавлено в версии 3.14.

annotationlib.get_annotate_from_class_namespace(namespace)

Извлекает функцию annotate из словаря пространства имён класса namespace. Возвращает None, если словарь пространства имён не содержит функцию annotate. В первую очередь это полезно до полного создания класса (например, в метаклассе); после создания класса функцию annotate можно получить с помощью cls.__annotate__. Пример использования этой функции в метаклассе см. ниже.

Добавлено в версии 3.14.

annotationlib.get_annotations(obj, *, globals=None, locals=None, eval_str=False, format=Format.VALUE)

Вычисляет словарь аннотаций объекта.

obj может быть вызываемым объектом, классом, модулем или другим объектом с атрибутами __annotate__ или __annotations__. Передача любого другого объекта вызывает исключение TypeError.

Параметр format определяет формат возвращаемых аннотаций и должен быть членом перечисления Format или его целочисленным эквивалентом. Различные форматы работают следующим образом:

  • VALUE: сначала проверяется object.__annotations__; если он отсутствует, вызывается функция object.__annotate__, если она существует.
  • FORWARDREF: если object.__annotations__ существует и успешно вычисляется, используется он; в противном случае вызывается функция object.__annotate__. Если она также отсутствует, выполняется повторная попытка получить object.__annotations__, и возникающая при этом ошибка повторно возбуждается.

    • При вызове object.__annotate__ сначала вызывается с FORWARDREF. Если этот формат не реализован, затем проверяется поддержка VALUE_WITH_FAKE_GLOBALS, и он используется в окружении с фиктивными глобальными переменными. Если ни один из этих форматов не поддерживается, используется запасной вариант — VALUE. Если VALUE завершается ошибкой, будет возбуждено исключение, возникшее при этом вызове.
  • STRING: если object.__annotate__ существует, сначала вызывается он; в противном случае используется object.__annotations__, а результат преобразуется в строку с помощью annotations_to_string().

    • При вызове object.__annotate__ сначала вызывается с STRING. Если этот формат не реализован, затем проверяется поддержка VALUE_WITH_FAKE_GLOBALS, и он используется в окружении с фиктивными глобальными переменными. Если ни один из этих форматов не поддерживается, используется запасной вариант — VALUE, а результат преобразуется с помощью annotations_to_string(). Если VALUE завершается ошибкой, будет возбуждено исключение, возникшее при этом вызове.

Возвращает словарь. get_annotations() при каждом вызове возвращает новый словарь; два вызова для одного и того же объекта вернут два разных, но эквивалентных словаря.

Эта функция берет на себя выполнение нескольких задач:

  • Если значение eval_str равно true, значения типа str преобразуются из строк с помощью eval(). Это предназначено для использования со строковыми аннотациями (from __future__ import annotations). Установка eval_str в true с форматами, отличными от Format.VALUE, приводит к ошибке.
  • Если у obj нет словаря аннотаций, возвращает пустой словарь. (У функций и методов словарь аннотаций есть всегда; у классов, модулей и других типов вызываемых объектов его может не быть.)
  • Игнорирует унаследованные аннотации классов, а также аннотации метаклассов. Если у класса нет собственного словаря аннотаций, возвращает пустой словарь.
  • Для безопасности все обращения к членам объектов и значениям словарей выполняются с помощью getattr() и dict.get().

Параметр eval_str определяет, заменяются ли значения типа str результатом вызова eval() для этих значений:

  • Если eval_str имеет значение true, для значений типа str вызывается eval(). (Обратите внимание: get_annotations() не перехватывает исключения; если eval() вызывает исключение, оно развернет стек вызовов за пределы вызова get_annotations().)
  • Если eval_str имеет значение false (по умолчанию), значения типа str остаются без изменений.

Параметры globals и locals передаются в eval(); дополнительную информацию см. в документации к eval(). Если globals или locals равен None, эта функция может заменить это значение контекстно-зависимым значением по умолчанию в зависимости от type(obj):

  • Если obj — модуль, для globals по умолчанию используется obj.__dict__.
  • Если obj — класс, для globals по умолчанию используется sys.modules[obj.__module__].__dict__, а для locals — пространство имён класса obj.
  • Если obj — вызываемый объект, для globals по умолчанию используется obj.__globals__; однако если obj — обёрнутая функция (с использованием functools.update_wrapper()) или объект functools.partial, выполняется снятие обёрток до обнаружения функции без обёртки.

Для доступа к словарю аннотаций любого объекта рекомендуется вызывать get_annotations(). Дополнительную информацию о рекомендуемых способах работы с аннотациями см. в разделе Рекомендации по работе с аннотациями.

>>> def f(a: int, b: str) -> float:
...     pass
>>> get_annotations(f)
{'a': <class 'int'>, 'b': <class 'str'>, 'return': <class 'float'>}

Добавлено в версии 3.14.

annotationlib.type_repr(value)

Преобразует произвольное значение Python в формат, подходящий для использования в формате STRING. Для большинства объектов вызывается repr(), однако для некоторых объектов, например объектов типов, предусмотрена специальная обработка.

Эта функция предназначена для использования в качестве вспомогательной функции для пользовательских функций annotate, поддерживающих формат STRING, но не имеющих доступа к коду, создающему аннотации. Её также можно использовать для получения удобного для пользователя строкового представления других объектов, содержащих значения, часто встречающиеся в аннотациях.

Добавлено в версии 3.14.

Примеры

Использование аннотаций в метаклассе

Метаклассу может потребоваться проверить или даже изменить аннотации в теле класса во время его создания. Для этого необходимо извлечь аннотации из словаря пространства имён класса. У классов, созданных с помощью from __future__ import annotations, аннотации находятся в ключе __annotations__ словаря. Для других классов с аннотациями можно использовать get_annotate_from_class_namespace(), чтобы получить функцию annotate, и call_annotate_function(), чтобы вызвать её и получить аннотации. Обычно лучше всего использовать формат FORWARDREF, поскольку он позволяет аннотациям ссылаться на имена, которые ещё нельзя разрешить при создании класса.

Чтобы изменить аннотации, рекомендуется создать функцию-обёртку annotate, которая вызывает исходную функцию annotate, вносит необходимые изменения и возвращает результат.

Ниже приведён пример метакласса, который отфильтровывает все аннотации typing.ClassVar класса и помещает их в отдельный атрибут:

import annotationlib
import typing

class ClassVarSeparator(type):
   def __new__(mcls, name, bases, ns):
      if "__annotations__" in ns:  # from __future__ import annotations
         annotations = ns["__annotations__"]
         classvar_keys = {
            key for key, value in annotations.items()
            # Use string comparison for simplicity; a more robust solution
            # could use annotationlib.ForwardRef.evaluate
            if value.startswith("ClassVar")
         }
         classvars = {key: annotations[key] for key in classvar_keys}
         ns["__annotations__"] = {
            key: value for key, value in annotations.items()
            if key not in classvar_keys
         }
         wrapped_annotate = None
      elif annotate := annotationlib.get_annotate_from_class_namespace(ns):
         annotations = annotationlib.call_annotate_function(
            annotate, format=annotationlib.Format.FORWARDREF
         )
         classvar_keys = {
            key for key, value in annotations.items()
            if typing.get_origin(value) is typing.ClassVar
         }
         classvars = {key: annotations[key] for key in classvar_keys}

         def wrapped_annotate(format):
            annos = annotationlib.call_annotate_function(annotate, format, owner=typ)
            return {key: value for key, value in annos.items() if key not in classvar_keys}

      else:  # no annotations
         classvars = {}
         wrapped_annotate = None
      typ = super().__new__(mcls, name, bases, ns)

      if wrapped_annotate is not None:
         # Wrap the original __annotate__ with a wrapper that removes ClassVars
         typ.__annotate__ = wrapped_annotate
      typ.classvars = classvars  # Store the ClassVars in a separate attribute
      return typ

Создание пользовательской вызываемой функции annotate

Пользовательские функции annotate могут быть обычными функциями, подобными тем, которые автоматически создаются для функций, классов и модулей. Также можно использовать инкапсуляцию, предоставляемую классами; в этом случае в качестве функции annotate можно использовать любой вызываемый объект.

Чтобы напрямую предоставлять форматы VALUE, STRING или FORWARDREF, функция annotate должна предоставлять следующий атрибут:

  • Вызываемый объект __call__ с сигнатурой __call__(format, /) -> dict, который при вызове с поддерживаемым форматом не вызывает исключение NotImplementedError.

Чтобы предоставлять формат VALUE_WITH_FAKE_GLOBALS, используемый для автоматической генерации STRING или FORWARDREF, если они не поддерживаются напрямую, функции annotate должны предоставлять следующие атрибуты:

  • Вызываемый объект __call__ с сигнатурой __call__(format, /) -> dict, который при вызове с VALUE_WITH_FAKE_GLOBALS не вызывает исключение NotImplementedError.
  • Объект кода __code__, содержащий скомпилированный код функции annotate.
  • Необязательно: кортеж позиционных значений по умолчанию функции __kwdefaults__, если функция, представленная объектом __code__, использует позиционные значения по умолчанию.
  • Необязательно: словарь значений по умолчанию для именованных аргументов функции __defaults__, если функция, представленная объектом __code__, использует значения по умолчанию для именованных аргументов.
  • Необязательно: все остальные атрибуты функции.
class Annotate:
    called_formats = []

    def __call__(self, format=None, /, *, _self=None):
        # When called with fake globals, `_self` will be the
        # actual self value, and `self` will be the format.
        if _self is not None:
            self, format = _self, self

        self.called_formats.append(format)
        if format <= 2:  # VALUE or VALUE_WITH_FAKE_GLOBALS
            return {"x": MyType}
        raise NotImplementedError

    __code__ = __call__.__code__
    __defaults__ = (None,)
    __kwdefaults__ = property(lambda self: dict(_self=self))

    __globals__ = {}
    __builtins__ = {}
    __closure__ = None

Затем её можно вызвать следующим образом:

>>> from annotationlib import call_annotate_function, Format
>>> call_annotate_function(Annotate(), format=Format.STRING)
{'x': 'MyType'}

Или использовать в качестве функции annotate объекта:

>>> from annotationlib import get_annotations, Format
>>> class C:
...   pass
>>> C.__annotate__ = Annotate()
>>> get_annotations(Annotate(), format=Format.STRING)
{'x': 'MyType'}

Ограничения формата STRING

Формат STRING предназначен для приближённого восстановления исходного кода аннотации, однако используемый метод реализации означает, что точный исходный код удаётся восстановить не всегда.

Во-первых, преобразователь в строку, разумеется, не может восстановить сведения, отсутствующие в скомпилированном коде, включая комментарии, пробелы, расстановку скобок и операции, упрощённые компилятором.

Во-вторых, преобразователь в строку может перехватывать почти все операции с именами, поиск которых выполняется в некотором пространстве имён, но не может перехватывать операции, выполняемые исключительно над константами. Следовательно, запрашивать формат STRING для недоверенного кода также небезопасно: Python достаточно мощный язык, чтобы выполнять произвольный код даже без доступа к глобальным переменным или встроенным именам. Например:

>>> def f(x: (1).__class__.__base__.__subclasses__()[-1].__init__.__builtins__["print"]("Hello world")): pass
...
>>> annotationlib.get_annotations(f, format=annotationlib.Format.STRING)
Hello world
{'x': 'None'}

Примечание

На момент написания этот конкретный пример работает, однако он зависит от деталей реализации и не гарантируется в будущих версиях.

Из различных видов выражений, существующих в Python и представленных модулем ast, одни поддерживаются, то есть формат STRING в целом может восстановить исходный код; другие не поддерживаются и могут привести к неверному результату или ошибке.

Поддерживаются следующие выражения (иногда с оговорками):

  • ast.BinOp
  • ast.UnaryOp

    • ast.Invert (~), ast.UAdd (+) и ast.USub (-) поддерживаются
    • ast.Not (not) не поддерживается
  • ast.Dict (за исключением использования распаковки **)
  • ast.Set
  • ast.Compare

    • ast.Eq и ast.NotEq поддерживаются
    • ast.Lt, ast.LtE, ast.Gt и ast.GtE поддерживаются, однако операнды могут поменяться местами
    • ast.Is, ast.IsNot, ast.In и ast.NotIn не поддерживаются
  • ast.Call (за исключением использования распаковки **)
  • ast.Constant (но не точное представление константы; например, экранирующие последовательности в строках теряются, а шестнадцатеричные числа преобразуются в десятичные)
  • ast.Attribute (если значение не является константой)
  • ast.Subscript (если значение не является константой)
  • ast.Starred (распаковка *)
  • ast.Name
  • ast.List
  • ast.Tuple
  • ast.Slice

Следующие выражения не поддерживаются, но при их обнаружении преобразователь в строку выдаёт информативную ошибку:

  • ast.FormattedValue (f-строки; ошибка не обнаруживается при использовании спецификаторов преобразования, например !r)
  • ast.JoinedStr (f-строки)

Следующие выражения не поддерживаются и приводят к неверному результату:

  • ast.BoolOp (and и or)
  • ast.IfExp
  • ast.Lambda
  • ast.ListComp
  • ast.SetComp
  • ast.DictComp
  • ast.GeneratorExp

Следующие выражения запрещены в областях видимости аннотаций и поэтому не рассматриваются:

  • ast.NamedExpr (:=)
  • ast.Await
  • ast.Yield
  • ast.YieldFrom

Ограничения формата FORWARDREF

Формат FORWARDREF стремится по возможности возвращать реальные значения, заменяя всё, что не удаётся разрешить, объектами ForwardRef. На него распространяются в целом те же ограничения, что и на формат STRING: при вычислении аннотаций, выполняющих операции над литералами или использующих неподдерживаемые типы выражений, формат FORWARDREF может вызвать исключения.

Ниже приведено несколько примеров поведения с неподдерживаемыми выражениями:

>>> from annotationlib import get_annotations, Format
>>> def zerodiv(x: 1 / 0): ...
>>> get_annotations(zerodiv, format=Format.STRING)
Traceback (most recent call last):
  ...
ZeroDivisionError: division by zero
>>> get_annotations(zerodiv, format=Format.FORWARDREF)
Traceback (most recent call last):
  ...
ZeroDivisionError: division by zero
>>> def ifexp(x: 1 if y else 0): ...
>>> get_annotations(ifexp, format=Format.STRING)
{'x': '1'}

Последствия интроспекции аннотаций для безопасности

Многие возможности этого модуля предполагают выполнение кода, связанного с аннотациями, который может делать что угодно. Например, get_annotations() может вызвать произвольную функцию annotate, а ForwardRef.evaluate() может вызвать eval() для произвольной строки. Код, содержащийся в аннотации, может выполнять произвольные системные вызовы, входить в бесконечный цикл или выполнять любые другие действия. Это также относится к любому обращению к атрибуту __annotations__ и к различным функциям модуля typing, работающим с аннотациями, например typing.get_type_hints().

Любая связанная с этим проблема безопасности также возникает сразу после импорта кода, который может содержать недоверенные аннотации: импорт кода всегда может привести к выполнению произвольных операций. Однако небезопасно принимать строки или другие данные из недоверенного источника и передавать их любому API для интроспекции аннотаций, например редактируя словарь __annotations__ или напрямую создавая объект ForwardRef.

© 2001 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.14/library/annotationlib.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API