annotationlib — 주석(introspection) 검사 기능
annotationlib — 주석(introspection) 검사 기능
(3.14 추가, 소스: Lib/annotationlib.py)
annotationlib 모듈은 모듈·클래스·함수의 어노테이션을 검사(introspect)하는 도구를 제공해요. 어노테이션은 지연 평가되고, 종종 어노테이션이 생성될 당시 아직 정의되지 않은 객체에 대한 전방 참조(forward reference) 를 포함해요. 이 모듈은 그런 전방 참조와 기타 엣지 케이스가 있어도 어노테이션을 안정적으로 가져올 수 있는 저수준 도구들을 제공해요.
이 모듈은 어노테이션을 세 가지 주요 형식(Format) 으로 가져오는 걸 지원해요. 각각 용도가 달라요.
- VALUE: 어노테이션을 평가해서 그 값을 반환해요. 다루기 가장 직관적이지만, 어노테이션이 정의되지 않은 이름을 참조하면 오류가 날 수 있어요.
- FORWARDREF: 해석할 수 없는 어노테이션에 대해
ForwardRef객체를 반환해서, 어노테이션을 평가하지 않고 검사할 수 있어요. 미해결 전방 참조를 포함할 수 있는 어노테이션을 다룰 때 유용해요. - STRING: 어노테이션을 소스 파일에 나타나는 것처럼 문자열로 반환해요. 어노테이션을 읽기 쉽게 표시하고 싶은 문서 생성기에서 유용해요.
get_annotations() 함수가 어노테이션을 가져오는 주 진입점이에요. 함수·클래스·모듈이 주어지면 요청된 형식의 어노테이션 사전을 반환해요. 이 모듈은 어노테이션 평가에 쓰이는 annotate 함수를 직접 다루는 기능도 제공해요. get_annotate_from_class_namespace(), call_annotate_function(), 그리고 evaluate 함수용 call_evaluate_function()이 있어요.
주의: 이 모듈의 대부분 기능은 임의 코드를 실행할 수 있어요. 보안 섹션을 참고하세요.
참고: PEP 649가 파이썬에서 어노테이션이 작동하는 현재 모델을 제안했고, PEP 749가 다양한 측면을 확장하며 annotationlib 모듈을 도입했어요. "Annotations Best Practices" 문서가 어노테이션 작업 모범 사례를 제공해요.
본문
어노테이션 의미론 (Annotation semantics)
어노테이션이 평가되는 방식은 파이썬 3의 역사 동안 바뀌었고, 현재도 미래 import에 의존해요. 세 가지 실행 모델이 있어요.
- Stock semantics (파이썬 3.0~3.13 기본; PEP 3107, PEP 526 참고): 어노테이션은 소스 코드에서 마주치는 대로 즉시(eagerly) 평가돼요.
- 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__)
- stock semantics(3.13 이하)에서는
func가 정의된 줄에서NameError가 나요 (Cls가 그 시점에 정의되지 않은 이름이라서). - stringified 어노테이션(
from __future__ import annotations사용)에서는{'a': 'Cls', 'return': 'None'}을 출력해요. - deferred evaluation(3.14 이상)에서는
{'a': <class 'Cls'>, 'return': None}을 출력해요.
from __future__ import annotations가 있으면 여전히 stringified 어노테이션이 쓰이지만, 이 동작은 결국 제거될 예정이에요.
클래스 (Classes)
class annotationlib.Format
어노테이션이 반환될 수 있는 형식을 설명하는 IntEnum이에요. 이 enum의 멤버(또는 그에 해당하는 정수 값)를 get_annotations()와 이 모듈의 다른 함수들, 그리고 __annotate__ 함수에 넘길 수 있어요.
VALUE = 1: 어노테이션 표현식을 평가한 결과 값.VALUE_WITH_FAKE_GLOBALS = 2: annotate 함수가 가짜 globals를 가진 특수 환경에서 평가되고 있음을 알리는 데 쓰는 특수 값. 이 값을 받으면 annotate 함수는Format.VALUE와 같은 값을 반환하거나, 이 환경에서의 실행을 지원하지 않음을 알리는NotImplementedError를 발생시켜야 해요. 이 형식은 내부 전용이라 이 모듈의 함수에 넘기면 안 돼요.FORWARDREF = 3: 정의된 값은 실제 어노테이션 값(VALUE 형식대로), 정의되지 않은 값은ForwardRef프록시. 실제 객체가ForwardRef프록시를 참조할 수 있어요.STRING = 4: 소스 코드에 나타나는 어노테이션의 텍스트 문자열. (공백 정규화, 상수 값 최적화 등 수정을 포함하지만 이에 국한되진 않아요.) 이 문자열의 정확한 값은 향후 파이썬 버전에서 바뀔 수 있어요. (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같은 예외를 던질 수 있어요. FORWARDREF면 절대 예외를 던지지 않지만ForwardRef인스턴스를 반환할 수 있어요. STRING이면__forward_arg__를 반환해요.owner는 권장 스코프 전달 메커니즘이고(ForwardRef가 유래한 어노테이션을 담은 모듈/타입/함수 객체),globals·locals·type_params는 더 정밀하게 이름에 영향을 줘요. 제네릭 클래스의C.__type_params__처럼 형식 매개변수가 스코프에 있으면type_params를 설정해요. (3.14 추가)
함수 (Functions)
-
annotationlib.annotations_to_string(annotations): 런타임 값을 담은 어노테이션 사전을 문자열만 담은 사전으로 변환해요. 이미 문자열이 아니면type_repr()로 변환해요. STRING 형식을 지원하지만 소스를 만드는 코드에 접근할 수 없는 사용자 제공 annotate 함수를 위한 헬퍼예요. (3.14 추가) -
annotationlib.call_annotate_function(annotate, format, *, owner=None): 주어진format(Format enum 멤버)으로 annotate 함수annotate를 호출하고 그 함수가 만든 어노테이션 사전을 반환해요. 컴파일러가 함수·클래스·모듈에 대해 생성한 annotate 함수는 직접 호출하면 VALUE 형식만 지원하므로, 다른 형식을 지원하려면 이 헬퍼가 특수 환경에서 호출해요. 클래스가 구성되는 동안 어노테이션을 부분 평가해야 하는 기능 구현 시 유용한 빌딩 블록이에요. (3.14 추가) -
annotationlib.call_evaluate_function(evaluate, format, *, owner=None): 주어진 형식으로 evaluate 함수를 호출하고 그 값만 반환해요.call_annotate_function()과 비슷하지만 후자는 항상 사전을, 이 함수는 단일 값을 반환해요. 지연 평가되는 타입 별칭·타입 매개변수 관련 요소(typing.TypeAliasType.evaluate_value(),typing.TypeVar.evaluate_bound()/evaluate_constraints()/evaluate_default(),typing.ParamSpec.evaluate_default(),typing.TypeVarTuple.evaluate_default())에 쓰기 위한 것이에요. (3.14 추가) -
annotationlib.get_annotate_from_class_namespace(namespace): 클래스 네임스페이스 사전namespace에서 annotate 함수를 가져와요. 없으면None반환. 클래스가 완전히 생성되기 전(예: 메타클래스에서) 주로 유용하고, 클래스가 있으면cls.__annotate__로 가져올 수 있어요. (3.14 추가) -
annotationlib.get_annotations(obj, *, globals=None, locals=None, eval_str=False, format=Format.VALUE): 객체의 어노테이션 사전을 계산해요.obj는__annotate__또는__annotations__속성이 있는 콜러블·클래스·모듈 등이어야 하고, 그 외 객체는TypeError.format이 반환 형식을 제어해요. 형식별 동작:- VALUE:
object.__annotations__를 먼저 시도하고, 없으면object.__annotate__함수를 호출. - FORWARDREF:
__annotations__가 존재하고 성공적으로 평가되면 사용, 아니면__annotate__호출. - STRING:
__annotate__가 있으면 먼저 호출, 없으면__annotations__를annotations_to_string()으로 문자열화.
매번 호출할 때마다 새 사전을 반환해요.
eval_str이 true면 str 값들을eval()로 언스트링화해요 (stringified 어노테이션용; VALUE 외 형식과 함께면 오류). 어노테이션 사전이 없으면 빈 사전 반환. 클래스의 상속된 어노테이션과 메타클래스 어노테이션은 무시해요. 모든 접근은 안전을 위해getattr()과dict.get()으로 해요.globals/locals가 None이면type(obj)에 따라 기본값으로 대체돼요(모듈은obj.__dict__, 클래스는sys.modules[obj.__module__].__dict__와 클래스 네임스페이스, 콜러블은obj.__globals__). (3.14 추가) - VALUE:
-
annotationlib.type_repr(value): 임의 파이썬 값을 STRING 형식에 적합한 형식으로 변환해요. 대부분 객체는repr()을 호출하지만 type 객체 같은 일부 객체는 특수 처리가 있어요. 사용자 제공 annotate 함수용 헬퍼이자, 어노테이션에 자주 등장하는 값을 담은 다른 객체의 사용자 친화적 문자열 표현을 제공하는 데도 쓸 수 있어요. (3.14 추가)
레시피 (Recipes)
메타클래스에서 어노테이션 사용하기
메타클래스는 클래스 생성 중에 클래스 본문의 어노테이션을 검사하거나 수정하고 싶을 수 있어요. 그러려면 클래스 네임스페이스 사전에서 어노테이션을 가져와야 해요. from __future__ import annotations로 만든 클래스는 __annotations__ 키에 있고, 다른 클래스는 get_annotate_from_class_namespace()로 annotate 함수를 얻고 call_annotate_function()으로 호출해요. 보통 FORWARDREF 형식이 가장 좋은데, 클래스가 생성될 때 아직 해결할 수 없는 이름을 어노테이션이 참조할 수 있게 하기 때문이에요.
어노테이션을 수정하려면 원래 annotate 함수를 호출하고 필요한 조정을 해서 결과를 반환하는 래퍼 annotate 함수를 만드는 게 좋아요. (예제: 클래스에서 typing.ClassVar 어노테이션을 걸러 별도 속성에 넣는 메타클래스.)
사용자 정의 콜러블 annotate 함수 만들기
사용자 정의 annotate 함수는 함수·클래스·모듈용으로 자동 생성된 것 같은 일반 함수일 수도 있고, 클래스의 캡슐화를 활용하고 싶다면 어떤 콜러블이든 annotate 함수로 쓸 수 있어요. VALUE/STRING/FORWARDREF 형식을 직접 제공하려면 __call__(format, /) -> dict 시그니처의 콜러블 __call__(지원 형식 호출 시 NotImplementedError를 던지지 않아야 함)이 필요해요. VALUE_WITH_FAKE_GLOBALS를 제공하려면 추가로 코드 객체 __code__, 위치 기본값 __kwdefaults__, 키워드 기본값 __defaults__(선택), 기타 함수 속성들이 필요해요.
STRING 형식의 한계
STRING 형식은 어노테이션의 소스 코드에 근사하려 하지만, 사용하는 구현 전략 때문에 정확한 소스 코드를 항상 복원할 수는 없어요. 컴파일된 코드에 없는 정보(주석, 공백, 괄호, 컴파일러가 단순화한 연산)는 복원할 수 없고, 상수에 완전히 작동하는 연산은 가로챌 수 없어요. 그래서 신뢰할 수 없는 코드에 STRING 형식을 요청하는 건 안전하지 않아요 — 파이썬은 globals/빌트인 접근이 없어도 임의 코드 실행이 가능할 만큼 강력해요.
FORWARDREF 형식의 한계
FORWARDREF 형식은 가능한 한 실제 값을 만들고, 해결할 수 없는 것은 ForwardRef 객체로 대체하는 걸 목표로 해요. STRING 형식과 대체로 같은 한계를 받아요. 리터럴에 연산을 하거나 지원되지 않는 표현식 타입을 쓰는 어노테이션은 FORWARDREF 형식으로 평가할 때 예외를 던질 수 있어요.
어노테이션 검사의 보안 영향
이 모듈의 많은 기능은 어노테이션 관련 코드를 실행하는데, 그게 임의의 일을 할 수 있어요. 예를 들어 get_annotations()는 임의의 annotate 함수를, ForwardRef.evaluate()는 임의의 문자열에 eval()을 호출할 수 있어요. 어노테이션에 담긴 코드는 임의의 시스템 호출, 무한 루프, 어떤 연산이든 할 수 있어요. __annotations__ 속성 접근과 typing.get_type_hints() 같은 typing의 여러 함수도 마찬가지예요. 신뢰할 수 없는 소스의 문자열이나 다른 입력을 받아 어노테이션 검사 API에 넘기는 것은 안전하지 않아요(예: __annotations__ 사전을 수정하거나 ForwardRef 객체를 직접 만드는 것).