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

annotationlib – Функциональность для интроспекции аннотацийannotationlib – Functionality for introspecting annotations

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

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


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

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

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

  • VALUE вычисляет аннотации и возвращает их значение. С ним проще всего работать, но он может вызывать ошибки, например, если аннотации содержат ссылки на неопределённые имена.

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

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

The get_annotations() function is the main entry point for retrieving annotations. Given a function, class, or module, it returns an annotations dictionary in the requested format. This module also provides functionality for working directly with the annotate function that is used to evaluate annotations, such as get_annotate_from_class_namespace() and call_annotate_function(), as well as the call_evaluate_function() function for working with evaluate functions.

Внимание

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

См. также

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

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

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

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

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

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

  • Стандартная семантика (по умолчанию в 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 и ранее) будет выброшено NameError в строке, где определён func, поскольку 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, но это поведение будет со временем удалено.

КлассыClasses

class annotationlib.Format

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

VALUE = 1

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

VALUE_WITH_FAKE_GLOBALS = 2

Специальное значение, которое используется для сигнализации о том, что функция аннотации выполняется в специальном окружении с поддельными глобальными переменными. При передаче этого значения функции аннотации должны либо вернуть то же значение, что и для формата 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.

ФункцииFunctions

annotationlib.annotations_to_string(annotations)

Преобразует словарь аннотаций, содержащий значения времени выполнения, в словарь, содержащий только строки. Если значения ещё не являются строками, они преобразуются с помощью type_repr(). Это предназначено в качестве помощника для пользовательских функций аннотации, которые поддерживают формат 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 с заданным format, членом перечисления Format, и возвращает словарь аннотаций, полученный от функции.

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

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

См. также

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

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

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

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

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

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, и используется в поддельном окружении globals. Если ни один из этих форматов не поддерживается, используется VALUE. Если VALUE завершается ошибкой, возбуждается ошибка от этого вызова.

  • STRING: Если object.__annotate__ существует, сначала вызывается он; в противном случае используется object.__annotations__ и преобразуется в строку с помощью annotations_to_string().

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

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

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

  • Если eval_str истинно, значения типа str будут преобразованы из строки с помощью eval(). Это предназначено для использования со строковыми аннотациями (from __future__ import annotations). Ошибка – устанавливать eval_str в true с форматами, отличными от Format.VALUE.

  • Если obj не имеет словаря аннотаций, возвращается пустой словарь. (Функции и методы всегда имеют словарь аннотаций; классы, модули и другие типы вызываемых объектов – не обязательно.)

  • Игнорирует унаследованные аннотации в классах, а также аннотации в метаклассах. Если класс не имеет собственного словаря аннотаций, возвращается пустой словарь.

  • Все обращения к членам объекта и значениям словаря выполняются с использованием getattr() и dict.get() для безопасности.

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

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

  • Если eval_str ложно (по умолчанию), значения типа 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(), но для некоторых объектов, например объектов-типов, применяется специальная обработка.

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

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

РецептыRecipes

Использование аннотаций в метаклассеUsing annotations in a metaclass

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

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

Ниже приведён пример метакласса, который отфильтровывает все аннотации 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()
            # Для простоты используется строковое сравнение; более надёжное решение
            # может использовать 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:  # нет аннотаций
         classvars = {}
         wrapped_annotate = None
      typ = super().__new__(mcls, name, bases, ns)

      if wrapped_annotate is not None:
         # Оборачивается оригинальный __annotate__ в обёртку, удаляющую ClassVars
         typ.__annotate__ = wrapped_annotate
      typ.classvars = classvars  # ClassVars сохраняются в отдельном атрибуте
      return typ

Создание пользовательской вызываемой аннотирующей функцииCreating a custom callable annotate function

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

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

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

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

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

  • Объект кода __code__, содержащий скомпилированный код для аннотирующей функции.

  • Необязательно: кортеж позиционных значений по умолчанию функции __kwdefaults__, если функция, представленная __code__, использует какие-либо позиционные значения по умолчанию.

  • Необязательно: словарь именованных значений по умолчанию функции __defaults__, если функция, представленная __code__, использует какие-либо именованные значения по умолчанию.

  • Необязательно: все остальные атрибуты функции.

class Annotate:
    called_formats = []

    def __call__(self, format=None, /, *, _self=None):
        # При вызове с fake globals, `_self` будет
        # фактическим значением self, а `self` будет форматом.
        if _self is not None:
            self, format = _self, self

        self.called_formats.append(format)
        if format <= 2:  # VALUE или 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'}

Или использовать в качестве аннотирующей функции для объекта:

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

Ограничения формата STRINGLimitations of the STRING format

Формат 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.FormattedValue (f-строки; ошибка не обнаруживается, если используются спецификаторы преобразования, такие как !r)

  • ast.JoinedStr (f-строки)

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

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

Ограничения формата FORWARDREFLimitations of the FORWARDREF format

Формат 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'}

Последствия для безопасности при интроспекции аннотацийSecurity implications of introspecting annotations

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

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