Python

annotationlib — 어노테이션 인트로스펙션 기능

Added in version 3.14.

소스 코드: Lib/annotationlib.py


annotationlib 모듈은 모듈, 클래스 및 함수에 대한 어노테이션 을 인트로스펙션하는 도구를 제공합니다.

어노테이션은 지연 평가 되며, 생성 시점에 아직 정의되지 않은 객체에 대한 전방 참조(forward reference)를 포함하는 경우가 많습니다. 이 모듈은 전방 참조 및 기타 예외적인 상황에서도 어노테이션을 안정적으로 가져올 수 있는 일련의 낮은 수준의 도구들을 제공합니다.

이 모듈은 세 가지 주요 형식으로 어노테이션을 가져오는 기능을 지원하며(Format 참조), 각 형식은 서로 다른 유스케이스에 적합합니다.

  • VALUE 는 어노테이션을 평가하여 그 값을 반환합니다. 사용하기 가장 직관적이지만, 예를 들어 어노테이션에 정의되지 않은 이름이 포함된 경우 오류가 발생할 수 있습니다.

  • FORWARDREF 는 해결할 수 없는 어노테이션에 대해 ForwardRef 객체를 반환하여 평가 없이 어노테이션을 검사할 수 있게 합니다. 이는 해결되지 않은 전방 참조가 포함된 어노테이션을 다루어야 할 때 유용합니다.

  • STRING 은 어노테이션을 소스 파일에 표시되는 것과 유사하게 문자열로 반환합니다. 이는 가독성 있게 어노테이션을 표시하고자 하는 문서 생성기에 유용합니다.

get_annotations() 함수는 어노테이션을 가져오는 주요 진입점입니다. 함수, 클래스 또는 모듈이 주어지면 요청된 형식으로 어노테이션 딕셔너리를 반환합니다. 또한 이 모듈은 annotate function 과 직접 작업하기 위한 기능(get_annotate_from_class_namespace(), call_annotate_function()) 및 evaluate functions 와 작업하기 위한 call_evaluate_function() 함수를 제공합니다.

조심

이 모듈의 대부분 기능은 임의의 코드를 실행할 수 있습니다. 자세한 내용은 보안 섹션 을 참조하십시오.

더 보기

PEP 649 은 파이썬에서 어노테이션이 작동하는 현재 모델을 제안했습니다.

PEP 749PEP 649 의 여러 측면을 확장하고 annotationlib 모듈을 도입했습니다.

어노테이션 모범 사례 는 어노테이션을 다루는 최적의 방법을 제공합니다.

typing-extensions 은 이전 버전의 파이썬에서도 작동하는 get_annotations() 의 백포트를 제공합니다.

어노테이션 의미론

어노테이션이 평가되는 방식은 파이썬 3의 역사에 따라 변화해 왔으며, 현재도 future import 에 의존합니다. 지금까지 존재했던 어노테이션 실행 모델은 다음과 같습니다.

  • 기본 의미론(Stock semantics) (파이썬 3.0~3.13 기본값; PEP 3107, PEP 526 참조): 어노테이션은 소스 코드에서 마주치는 대로 즉시 평가됩니다.

  • 문자열화된 어노테이션(Stringified annotations) (파이썬 3.7 이상에서 from __future__ import annotations 와 함께 사용; PEP 563 참조): 어노테이션이 문자열로만 저장됩니다.

  • 지연 평가(Deferred evaluation) (파이썬 3.14 이상 기본값; PEP 649, PEP 749 참조): 어노테이션은 접근할 때만 지연 평가됩니다.

예로, 다음 프로그램을 살펴보십시오:

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

class Cls: pass

print(func.__annotations__)

이 코드는 다음과 같이 작동합니다.

  • 기본 의미론(파이썬 3.13 이하)에서는 Cls 가 해당 시점에 정의되지 않은 이름이므로 func 가 정의되는 줄에서 NameError 를 발생시킵니다.

  • 문자열화된 어노테이션(from __future__ import annotations 사용 시)에서는 {'a': 'Cls', 'return': 'None'} 를 출력합니다.

  • 지연 평가(파이썬 3.14 이상)에서는 {'a': <class 'Cls'>, 'return': None} 를 출력합니다.

Python 3.0에서 함수 어노테이션이 처음 도입될 때(PEP 3107) 기본(stock) 시맨틱스가 사용되었는데, 이는 어노테이션을 구현하는 가장 간단하고 명확한 방법이었기 때문입니다. 변수 어노테이션이 도입된 Python 3.6(PEP 526)에서도 동일한 실행 모델이 사용되었습니다. 그러나 기본 시맨틱스는 어노테이션을 타입 힌트로 사용할 때, 어노테이션이 나타나는 시점에 아직 정의되지 않은 이름을 참조해야 하는 등의 문제를 야기했습니다. 또한 모듈 임포트 시점에 어노테이션을 실행할 때 성능 문제가 발생했습니다. 이에 따라 Python 3.7에서는 PEP 563 을 통해 from __future__ import annotations 구문을 사용하여 어노테이션을 문자열로 저장하는 기능이 도입되었습니다. 당시 계획은 이 동작을 결국 기본값으로 만드는 것이었으나, 한 가지 문제가 발생했습니다. 문자열화된 어노테이션은 런타임에 어노테이션을 조사(introspection)하는 경우 처리하기가 더 어려워진다는 점입니다. 대안으로 제시된 PEP 649 는 세 번째 실행 모델인 지연 평가를 도입했으며, 이는 Python 3.14에서 구현되었습니다. from __future__ import annotations 가 있는 경우 여전히 문자열화된 어노테이션이 사용되지만, 이 동작은 결국 제거될 예정입니다.

클래스

class annotationlib.Format

어노테이션이 반환될 수 있는 형식을 기술하는 IntEnum 입니다. 이 열거형의 멤버 또는 그에 해당하는 정수 값을 get_annotations() 및 이 모듈의 다른 함수뿐만 아니라 __annotate__ 함수에도 전달할 수 있습니다.

VALUE = 1

값은 어노테이션 표현식을 평가한 결과입니다.

VALUE_WITH_FAKE_GLOBALS = 2

가짜 전역 변수(fake globals)를 가진 특별한 환경에서 어노테이션 함수가 평가되고 있음을 알리는 특수 값입니다. 이 값이 전달되면, 어노테이션 함수는 Format.VALUE 형식과 동일한 값을 반환하거나 해당 환경에서의 실행을 지원하지 않는다는 의미로 NotImplementedError 를 발생시켜야 합니다. 이 형식은 내부적으로만 사용되며 이 모듈의 함수에 전달해서는 안 됩니다.

FORWARDREF = 3

값은 정의된 값에 대해서는 실제 어노테이션 값(Format.VALUE 형식 기준), 정의되지 않은 값에 대해서는 ForwardRef 프록시입니다. 실제 객체는 ForwardRef 프록시 객체에 대한 참조를 포함할 수 있습니다.

STRING = 4

값은 소스 코드에 나타나는 어노테이션의 텍스트 문자열이며, 여기에는 공백 정규화 및 상수 값 최적화를 포함하되 이에 국한되지 않는 수정 사항이 반영됩니다.

이 문자열의 정확한 값은 향후 Python 버전에서 변경될 수 있습니다.

Added in version 3.14.

class annotationlib.ForwardRef

어노테이션 내 전방 참조(forward reference)를 위한 프록시 객체입니다.

이 클래스의 인스턴스는 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__ 를 반환합니다.

The owner parameter provides the preferred mechanism for passing scope information to this method. The owner of a ForwardRef is the object that contains the annotation from which the ForwardRef derives, such as a module object, type object, or function object.

globals, locals, type_params 파라미터는 ForwardRef 가 평가될 때 사용 가능한 이름에 영향을 미치는 더 정밀한 메커니즘을 제공합니다. globalslocalseval() 에 전달되어 해당 이름이 평가되는 전역 및 지역 네임스페이스를 나타냅니다. type_params 파라미터는 generic classesfunctions 의 기본 문법을 사용하여 생성된 객체와 관련이 있습니다. 이는 전방 참조가 평가되는 동안 스코프 내에 있는 type parameters 의 튜플입니다. 예를 들어, 제네릭 클래스 C 의 클래스 네임스페이스에서 찾은 어노테이션으로부터 가져온 ForwardRef 를 평가할 때, type_paramsC.__type_params__ 로 설정되어야 합니다.

get_annotations() 에 의해 반환된 ForwardRef 인스턴스는 자신이 시작된 범위(scope)에 대한 정보를 보유하므로, 다른 인자 없이 이 메서드를 호출하는 것만으로도 이러한 객체들을 평가하기에 충분할 수 있습니다. 다른 방식으로 생성된 ForwardRef 인스턴스는 범위에 대한 정보가 없을 수 있으므로 성공적으로 평가하려면 이 메서드에 인자를 전달해야 할 수도 있습니다.

owner, globals, locals 또는 type_params 가 제공되지 않고 ForwardRef 에 원점 정보가 포함되어 있지 않은 경우, 빈 globals 및 locals 딕셔너리가 사용됩니다.

Added in version 3.14.

함수:

annotationlib.annotations_to_string(annotations)

런타임 값이 포함된 어노테이션 딕셔너리를 문자열만 포함하는 딕셔너리로 변환합니다. 값이 아직 문자열이 아닌 경우 type_repr() 을 사용하여 변환됩니다. 이는 STRING 형식을 지원하지만 어노테이션을 생성한 코드에 접근할 수 없는 사용자가 제공한 annotate 함수를 위한 도우미로 설계되었습니다.

예를 들어, 이 기능은 함수형 구문을 통해 생성된 typing.TypedDict 클래스에 대해 STRING 을 구현하는 데 사용됩니다.

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

Added in version 3.14.

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

주어진 format ( Format 열거형의 멤버)과 함께 annotate function annotate 를 호출하고 함수에 의해 생성된 어노테이션 딕셔너리를 반환합니다.

이 도우미 함수는 필요한데, 컴파일러가 생성한 함수, 클래스 및 모듈용 annotate 함수는 직접 호출될 때 VALUE 형식만 지원하기 때문입니다. 다른 형식을 지원하기 위해 이 함수는 해당 기능들이 다른 형식의 어노테이션을 생성할 수 있도록 허용하는 특별한 환경에서 annotate 함수를 호출합니다. 이는 클래스가 구성되는 동안 어노테이션을 부분적으로 평가해야 하는 기능을 구현할 때 유용한 빌딩 블록이 됩니다.

owner 은 어노테이션 함수를 소유하는 객체로, 일반적으로 함수, 클래스 또는 모듈입니다. 제공되는 경우, 이는 더 많은 정보를 포함하는 ForwardRef 객체를 생성하기 위해 FORWARDREF 형식에서 사용됩니다.

더 보기

PEP 649 에 이 함수가 사용하는 구현 기술에 대한 설명이 포함되어 있습니다.

Added in version 3.14.

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

주어진 format ( Format 열거형의 멤버)와 함께 evaluate function evaluate 를 호출하고 함수에 의해 생성된 값을 반환합니다. 이는 call_annotate_function() 과 유사하지만, 후자는 항상 문자열을 어노테이션으로 매핑하는 딕셔너리를 반환하는 반면 이 함수는 단일 값을 반환합니다.

이 기능은 타입 에일리어스 및 타입 파라미터와 관련된 지연 평가 요소에 대해 생성된 evaluate 함수들과 함께 사용하기 위한 것입니다:

owner 은 평가 함수를 소유하는 객체로, 타입 에일리어스나 타입 변수 객체와 같은 것입니다.

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'

Added in version 3.14.

annotationlib.get_annotate_from_class_namespace(namespace)

클래스 네임스페이스 딕셔너리 namespace 에서 annotate function 을 가져옵니다. 네임스페이스에 어노테이션 기능이 포함되어 있지 않으면 None 을 반환합니다. 이는 주로 클래스가 완전히 생성되기 전(예: 메타클래스 내)에 유용하며, 클래스가 존재한 이후에는 cls.__annotate__ 로 해당 기능을 가져올 수 있습니다. 메타클래스에서 이 함수를 사용하는 예는 아래 를 참조하십시오.

Added in version 3.14.

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

객체에 대한 어노테이션 딕셔너리를 계산합니다.

obj 은 호출 가능 객체(callable), 클래스, 모듈 또는 __annotate____annotations__ 어트리뷰스를 가진 다른 객체일 수 있습니다. 그 외의 객체를 전달하면 TypeError 가 발생합니다.

format 파라미터는 어노테이션이 반환되는 형식을 제어하며, Format 열거형의 멤버 또는 그에 해당하는 정수 값이어야 합니다. 각 형식은 다음과 같이 작동합니다.

  • VALUE: 먼저 object.__annotations__`가 시도됩니다. 해당 어트리뷰트가 존재하지 않으면 :attr:!object.__annotate__` 함수가 존재할 경우 호출됩니다.

  • FORWARDREF: object.__annotations__ 가 존재하고 성공적으로 평가될 수 있으면 이를 사용하며, 그렇지 않으면 object.__annotate__ 함수를 호출합니다. 이 또한 존재하지 않는 경우 다시 한번 object.__annotations__ 를 시도하며 이때 발생하는 오류는 그대로 재발생(re-raised)됩니다.

    • 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() 을 사용하여 문자열 해제(un-stringized)됩니다. 이는 문자열화된 어노테이션(from __future__ import annotations)과 함께 사용하도록 설계되었습니다. Format.VALUE 이외의 형식에서 eval_str 을 true로 설정하면 오류가 발생합니다.

  • obj 가 어노테이션 딕셔너리를 가지고 있지 않으면 빈 딕셔너리를 반환합니다. (함수와 메서드는 항상 어노테이션 딕셔너리가 있지만, 클래스나 모듈 및 기타 유형의 callable은 그렇지 않을 수 있습니다.)

  • 클래스에서 상속된 어노테이션이나 메타클래스의 어노테이션은 무시합니다. 클래스가 자체 어노테이션 딕셔너리를 가지고 있지 않으면 빈 딕셔너리를 반환합니다.

  • 안전성을 위해 객체 멤버 및 딕셔너리 값에 대한 모든 접근은 getattr()dict.get() 을 사용하여 수행됩니다.

eval_strstr 타입의 값을 해당 값에 대한 eval() 호출 결과로 대체할지 여부를 제어합니다.

  • eval_str이 true이면 str 타입의 값에 대해 eval`이 호출됩니다. (참고: :func:()!get_annotations`는 예외를 잡지 않으므로, 만약 eval`이 예외를 발생시키면 :func:()!get_annotations` 호출을 지나 스택이 풀리게 됩니다.)

  • eval_str 이 false(기본값)인 경우, str 타입의 값은 변경되지 않습니다.

globalslocalseval() 에 전달됩니다. 자세한 내용은 eval() 문서를 참조하십시오. globals 또는 localsNone 인 경우 이 함수는 type(obj) 에 따라 해당 값을 컨텍스트에 특화된 기본값으로 대체할 수 있습니다.

  • obj 이 모듈인 경우, globalsobj.__dict__ 를 기본값으로 합니다.

  • obj*이 클래스인 경우, *globals*는 ``sys.modules[obj.__module__].__dict__``를, *locals*는 *obj 클래스의 네임스페이스를 기본값으로 합니다.

  • obj 이 callable인 경우, globalsobj.__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'>}

Added in version 3.14.

annotationlib.type_repr(value)

임의의 파이썬 값을 STRING 형식에서 사용할 수 있는 형태로 변환합니다. 대부분의 객체에 대해 repr() 을 호출하지만, 타입 객체와 같은 일부 객체는 특별하게 처리됩니다.

이 기능은 STRING 형식을 지원하지만 어노테이션을 생성하는 코드에 접근할 수 없는 사용자가 제공한 annotate 함수를 위한 도우미입니다. 또한 어노테이션에서 흔히 발견되는 값을 포함하는 다른 객체들에 대해 사용자 친화적인 문자열 표현을 제공하기 위해 사용할 수도 있습니다.

Added in version 3.14.

레시피

메타클래스에서 어노테이션 사용하기

A metaclass may want to inspect or even modify the annotations in a class body during class creation. Doing so requires retrieving annotations from the class namespace dictionary. For classes created with from __future__ import annotations, the annotations will be in the __annotations__ key of the dictionary. For other classes with annotations, get_annotate_from_class_namespace() can be used to get the annotate function, and call_annotate_function() can be used to call it and retrieve the annotations. Using the FORWARDREF format will usually be best, because this allows the annotations to refer to names that cannot yet be resolved when the class is created.

어노테이션을 수정하려면 원래의 어노테이트 함수를 호출하고 필요한 조정을 거친 후 결과를 반환하는 래퍼(wrapper) 어노테이트 함수를 생성하는 것이 가장 좋습니다.

다음은 클래스에서 모든 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:
         # ClassVar를 제거하는 래퍼로 원본 __annotate__를 감쌉니다.
         typ.__annotate__ = wrapped_annotate
      typ.classvars = classvars  # ClassVar들을 별도의 어트리뷰트에 저장합니다.
      return typ

사용자 정의 호출 가능한 어노테이트 함수 생성

Custom annotate functions may be literal functions like those automatically generated for functions, classes, and modules. Or, they may wish to utilise the encapsulation provided by classes, in which case any callable can be used as an annotate function.

To provide the VALUE, STRING, or FORWARDREF formats directly, an annotate function must provide the following attribute:

  • 지원되는 형식으로 호출될 때 NotImplementedError 를 발생시키지 않는, 서명이 __call__(format, /) -> dict 인 호출 가능한 __call__ 입니다.

VALUE_WITH_FAKE_GLOBALS 형식을 제공하려면(이 형식은 직접 지원되지 않는 경우에 STRING 또는 FORWARDREF 를 자동으로 생성하는 데 사용됩니다), 어노테이트 함수 는 다음 어트리뷰션들을 제공해야 합니다:

  • VALUE_WITH_FAKE_GLOBALS 로 호출될 때 NotImplementedError 를 발생시키지 않는, 서명이 __call__(format, /) -> dict 인 호출 가능한 __call__ 입니다.

  • 어노테이트 함수의 컴파일된 코드를 포함하는 코드 객체 __code__ 입니다.

  • 선택 사항: __code__``가 나타내는 함수가 위치 인자 기본값을 사용하는 경우, 해당 함수의 위치 기본값인 ``__kwdefaults__ 튜플입니다.

  • 선택 사항: __code__ 가 나타내는 함수가 키워드 기본값을 사용하는 경우, 해당 함수의 키워드 기본값인 __defaults__ 를 포함하는 딕셔너리.

  • 선택 사항: 기타 모든 함수 어트리뷰트.

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'}

STRING 형의 한계

STRING 형식은 어노테이션의 소스 코드를 근사치로 재현하기 위한 것이지만, 사용된 구현 방식 때문에 정확한 소스 코드를 항상 복구할 수 있는 것은 아닙니다.

첫째로, 당연히 문자열 변환기(stringifier)는 주석, 공백, 괄호 사용 및 컴파일러에 의해 단순화되는 연산 등 컴파일된 코드에 포함되지 않은 어떤 정보도 복구할 수 없습니다.

둘째로, 문자열 변환기는 특정 범위 내에서 조회되는 이름을 포함하는 거의 모든 연산을 가로챌 수 있지만, 상수로만 구성된 연산은 가로채지 못합니다. 결과적으로 이는 신뢰할 수 없는 코드에 대해 STRING 형식을 요청하는 것이 안전하지 않음을 의미합니다. 파이썬은 매우 강력하기 때문에 전역 변수나 내장 함수에 대한 접근 권한이 전혀 없더라도 임의의 코드를 실행하는 것이 가능할 수 있습니다. 예를 들면 다음과 같습니다:

>>> 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'}

참고

이 특정 예제는 작성 시점에는 작동하지만 구현 세부 사항에 의존하므로 향후에도 작동이 보장되지는 않습니다.

ast 모듈이 나타내는 파이썬의 다양한 종류의 표현식 중 일부는 지원되므로 STRING 형식이 일반적으로 원래 소스 코드를 복구할 수 있지만, 다른 것들은 지원되지 않으므로 잘못된 출력이 나오거나 오류가 발생할 수 있습니다.

다음 항목들이 지원됩니다(때로는 주의 사항이 있을 수 있음):

다음 항목들은 지원되지 않지만, 문자열 변환기에서 발견될 경우 유의미한 오류를 발생시킵니다:

다음 항목들은 지원되지 않으며 잘못된 출력을 생성합니다:

다음 항목들은 어노테이션 범위 내에서 허용되지 않으므로 해당 사항이 없습니다:

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'}

어노테이션 분석 시의 보안 영향

Much of the functionality in this module involves executing code related to annotations, which can then do arbitrary things. For example, get_annotations() may call an arbitrary annotate function, and ForwardRef.evaluate() may call eval() on an arbitrary string. Code contained in an annotation might make arbitrary system calls, enter an infinite loop, or perform any other operation. This is also true for any access of the __annotations__ attribute, and for various functions in the typing module that work with annotations, such as typing.get_type_hints().

이로 인해 발생하는 모든 보안 문제는 신뢰할 수 없는 어노테이션이 포함될 수 있는 코드를 임포트한 직후에도 즉시 적용됩니다. 코드 임포트는 항상 임의의 동작을 수행하게 할 수 있습니다. 그러나 신뢰할 수 없는 소스로부터 문자열이나 다른 입력을 받아 __annotations__ 딕셔너리를 편집하거나 ForwardRef 객체를 직접 생성하는 등 어노테이션 분석용 API에 전달하는 것은 안전하지 않습니다.