Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 835 – Annotated 타입 메타데이터를 위한 축약 문법

Author:
Till Varoquaux <till.varoquaux at gmail.com>
Sponsor:
Ivan Levkivskyi <levkivskyi at gmail.com>
Discussions-To:
Discourse thread
Status:
Draft
Type:
Standards Track
Topic:
Typing
Created:
12-Jun-2026
Python-Version:
3.16
Post-History:
19-Apr-2026, 18-Jun-2026

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

초록

이 PEP는 타입에 대해 @ 연산자를 오버로딩하여 Annotated[T, M]을 T @ M으로 쓸 수 있도록 할 것을 제안합니다.

동기

PEP 593 (Annotated)는 널리 채택되었습니다. 주요 프레임워크(FastAPI, Pydantic, SQLAlchemy, msgspec, Typer, beartype)는 현재 이를 표준 타입 메타데이터 메커니즘으로 사용하고 있습니다 [1].

그러나 장황한 문법은 여전히 채택을 가로막는 걸림돌로 남아 있습니다:

  1. 라이브러리들은 이를 숨기기 위해 별칭을 만들어 냅니다. 프레임워크들은 사용자가 Annotated[..., ...] 보일러플레이트를 직접 다루지 않도록 DSL이나 타입 별칭(예: PositiveInt)을 일상적으로 만듭니다 [2].
  2. 주요 프로젝트들은 채택을 미룹니다. PyTorch [3], TorchTyping, Beartype [4]는 텐서 형상에 Annotated를 도입하는 것을 미뤘으며, 그 이유로 장황한 문법을 명시적으로 언급하면서 언어 자체의 해결책을 기다려 왔습니다.
  3. 가독성에 영향을 줍니다. PEP 727 논의 중에 참여자들은 일상적인 문서화를 Annotated에 의존하면 코드를 읽을 수 없게 될 것이라고 경고했으며, 이는 해당 PEP가 철회되는 데 영향을 주었습니다 [5].

가장 많이 다운로드된 PyPI 프로젝트 10,000개 중 43.4%가 Annotated에 전이적으로 의존합니다. FastAPI 코드베이스를 새 축약 문법으로 옮기자 2,524줄(LOC)의 코드가 제거되었습니다.

컨테이너 형태의 문법은 Python의 발전 방향에 역행합니다. Python은 typing.Union 같은 제네릭 래퍼를 | 같은 연산자(그리고 교집합을 위해 제안된 &)로 적극적으로 대체하고 있으며, 타입 어노테이션을 타입의 산술로 발전시키고 있습니다.

@ 축약 문법은 문법을 의미와 일치시킵니다. 타입 메타데이터를 후위 연산자로 적용함으로써, 이 문법은 다음과 같은 이점을 제공합니다:

  • 중첩을 줄입니다 (괄호가 필요 없습니다).
  • 토큰을 줄입니다 (typing.Annotated 임포트가 필요 없습니다).
  • 지역성을 높입니다 (베이스 타입이 맨 앞의 중심에 그대로 유지됩니다).
  • 타입의 산술을 확장합니다 (타입 메타데이터가 제네릭 컨테이너가 아니라 기존 타입 표현식에 대한 연산으로 작용합니다).

이는 간결한 문법과 엄격한 타이핑 사이의 충돌을 해소합니다:

class User(BaseModel):
    # Bad: Early frameworks abused defaults, breaking static analysis
    id: int = Field(gt=0)

    # Verbose: PEP 593 fixed semantics, but hindered adoption
    id: Annotated[int, Field(gt=0)]

    # Proposed: Concise while preserving PEP 593 semantics
    id: int @ Field(gt=0)

연산자로서 @는 자연스럽게 조합됩니다:

# Inside generics
list[Annotated[str, Field(max_length=50)]]
list[str @ Field(max_length=50)]

# Within a union
Annotated[int, Ge(0)] | Annotated[str, Len(5)]
int @ Ge(0) | str @ Len(5)

# Across an entire union
Annotated[str | None, Field(description="Optional string")]
(str | None) @ Field(description="Optional string")

# Within callables
Callable[[Annotated[int, Ge(0)]], str]
Callable[[int @ Ge(0)], str]

역사적 맥락과 선행 사례

2023년 말 [6]과 2025년 [7]에 커뮤니티가 대안을 논의했을 때, 논의는 기존 Python 데코레이터와 시각적으로 일치한다는 이유로 @ 연산자 쪽으로 기울었습니다.

타입 메타데이터에 @를 채택하는 것은 제네릭에 []를 (PEP 585), 유니온에 |를 (PEP 604) 사용한 것처럼 런타임 연산자를 정적 타이핑 용도로 재활용하는 선례를 따르는 것이기도 합니다 [8].

이 후위 문법은 다른 정적 타입 언어의 기능을 반영합니다:

  • C++: 타입에 [[attribute]]를 사용합니다 (예: [[nodiscard]] int f();).
  • Java / Kotlin: @Annotation을 사용합니다 (예: List<@NonNull String> 또는 val x: @NotNull String).
  • OCaml: [@attribute] 후위 문법을 사용합니다 (예: type t = int [@default 0]).

용어

어노테이션과 메타데이터에 관한 논의를 명확히 하기 위해, 다음 용어를 사용합니다:

  • 타입 표현식: 예: x: <this>. 타이핑 명세에 명시된 대로, 타입 힌트 안에서 유효한 타입으로 평가되는 표현식입니다(PEP 484).
  • 타입 메타데이터: 예: int @ <this>. Annotated를 통해 타입 표현식에 붙는 데이터입니다(PEP 593, PEP 746).
  • 타이핑 외 어노테이션: 예: x: <this>를 감싸는 @no_type_check. 타입 표현식으로 평가되도록 의도되지 않은 어노테이션입니다(예: 타입 검사기가 무시하는 어노테이션).
  • 심볼 데코레이터: 메타데이터를 기저 타입이 아니라 변수 또는 필드 선언에 직접 적용하는 것입니다. 이러한 구분은 Java 같은 언어에서 잘 확립되어 있습니다:
    class Application {
        // Field Decorator (symbol decorator)
        @Inject
        private Service s;
    
        // Type Decorator (type metadata)
        private @NonNull String name;
    }
    
  • 심볼 메타데이터(또는 필드 메타데이터): 심볼에 붙는 메타데이터입니다. 이는 타입 자체가 아니라 변수의 특정 인스턴스에 적용된다는 점에서 타입 메타데이터와 구별됩니다.

명세

제안하는 문법은 타입에 메타데이터를 붙이기 위해 __matmul__ 연산자를 사용합니다:

# Current syntax
x: Annotated[int, Range(0, 10)]

# Proposed shorthand
x: int @ Range(0, 10)

연산자 우선순위

@가 | 유니언 연산자(PEP 604)보다 더 강하게 결합하므로, 괄호 없이도 유니언 안의 특정 타입에 서로 다른 제약을 직접 붙일 수 있습니다. 예를 들어 미국 우편번호는 5자리 정수이거나 5자 문자열일 수 있습니다:

zip_code: int @ Ge(10000) @ Le(99999) | str @ Len(5)

유니언 전체에 메타데이터를 붙이려면 괄호를 사용하십시오:

# Attaches only to 'str'
int | str @ Metadata    # equivalent to: int | Annotated[str, Metadata]

# Attaches to the entire union
(int | str) @ Metadata  # equivalent to: Annotated[int | str, Metadata]

여러 메타데이터 평탄화

연쇄된 메타데이터는 결과로 나오는 Annotated 객체를 평탄화합니다. T @ m1 @ m2는 Annotated[T, m1, m2]로 평가되며, 결코 Annotated[Annotated[T, m1], m2]로 평가되지 않습니다. 이는 typing.Annotated의 기존 런타임 동작과 일치합니다.

왼쪽 피연산자가 기존 Annotated 타입일 때에도 같은 논리가 적용됩니다:

Annotated[int, m1] @ m2  # AnnotatedType(int, m1, m2) (flattened)

런타임 동작

@는 types.AnnotatedType(새로운 내장 C 타입)을 생성합니다. 기존 typing.Annotated는 이 타입과 통합됩니다. typing.Annotated[X, Y]와 X @ Y는 정확히 같은 객체를 반환합니다:

>>> type(int @ Field()) is type(Annotated[int, Field()])
True
>>> typing.Annotated is types.AnnotatedType
True

AnnotatedType 객체는 다음 속성을 노출합니다:

  • __origin__: 베이스 타입입니다(예: int).
  • __metadata__: 메타데이터 항목들의 튜플입니다.
  • __args__: typing.get_args()와의 호환성을 위한 튜플 (origin, *metadata)입니다.
  • __parameters__: 해당 타입의 고유한 자유 타입 매개변수들로 이루어진 튜플입니다.

AnnotatedType의 repr()은 축약 문법을 사용합니다:

>>> int @ Field(gt=0)
int @ Field(gt=0)

None 처리

NoneType은 런타임 버그가 가려지는 것을 막기 위해 의도적으로 __matmul__을 구현하지 않습니다.

예를 들어 개발자가 행렬 곱셈 전에 None인지 확인하는 것을 잊을 수 있습니다:

 def matmul_arrays(a: np.array | None, b: np.array):
     return a @ b  # oops, forgot to check for None

NoneType.__matmul__이 존재했다면, 이 코드는 TypeError를 발생시키는 대신 조용히 AnnotatedType을 반환했을 것입니다.

NoneType에는 __matmul__이 없으므로, None @ Metadata 같은 표현식은 런타임에 TypeError를 발생시킵니다.

런타임에서 이를 안전하게 지원하려면 라이브러리 작성자는 메타데이터 객체에 __rmatmul__을 구현하는 것이 좋습니다. 이는 왼쪽 피연산자에서 __matmul__을 호출하지 않는 타입(NoneType이나 ForwardRef 등)에 대해 견고한 대체 수단을 제공합니다.

지원되는 왼쪽 피연산자

@ 연산자는 type과 | 유니언 연산자를 지원하는 모든 타이핑 구성 요소(types.GenericAlias, types.UnionType, types.AnnotatedType, typing.TypeVar, typing.ParamSpec, typing.TypeVarTuple, typing.TypeAliasType, typing.ForwardRef, sentinel 객체)에 nb_matrix_multiply를 추가합니다.

클래스에 @를 적용하면 타입 메타데이터로 평가되고, 인스턴스에 적용하면 산술 연산을 수행합니다.

예를 들어 int @ Field()는 AnnotatedType을 생성하는 반면, 42 @ something은 TypeError를 발생시킵니다(또는 __rmatmul__에 위임합니다).

마찬가지로, ndarray 인스턴스가 행렬 곱셈을 위해 __matmul__을 정의하더라도 ndarray @ Field()는 AnnotatedType을 생성합니다.

사용자 정의 메타클래스는 타입 표현식에서 @를 사용하지 않는 한 __matmul__을 오버로드할 수 있습니다.

근거

Annotated는 타입 메타데이터가 매개변수화된 클래스처럼 보이도록 강제합니다. 후위 연산자는 중첩 없이 동일한 의미를 제공합니다.

기존 @ 연산자를 재사용하므로 Python 파서는 변경되지 않습니다. 이 연산자의 새로운 의미는 타입 평가에만 국한되며, 여기서 T @ M은 Annotated[T, M]으로 변환됩니다. 타입 검사기(Mypy, Pyright)도 파서를 변경할 필요가 없으며, 의미 분석 단계에서 해당 문법을 직접 처리합니다. 자동 마이그레이션을 위한 Ruff 변환 규칙의 프로토타입을 만들었으며, CPython 프로토타입 테스트를 통해 typer와 pydantic 같은 라이브러리가 수정 없이 계속 동작함을 확인했습니다.

괄호와 장황함

address: (str | None) @Field(...)처럼 작성하면 네이티브 심볼 데코레이터가 없기 때문에 코드가 장황해집니다. 개발자의 의도는 str | None 타입이 아니라 address 필드를 구성하는 것인데도, 프레임워크는 필드를 구성하기 위해 Annotated를 차용합니다. @ 약식 표기가 이 구조적 한계를 완전히 없앨 수는 없지만, Annotated[str | None, Field(...)]에 비해 우회 방식의 구문적 부담을 크게 줄여 줍니다.

왜 @ 연산자인가?

메타데이터에 알맞은 연산자를 선택하려면 세 가지 고려 사항 사이에서 균형을 맞춰야 합니다.

  1. 우선순위: | (Union) 및 & (제안된 Intersection)보다 더 강하게 결합해야 int @ Field() | str 같은 표현식이 괄호 없이도 올바르게 파싱됩니다. 따라서 |, ^, & 같은 연산자는 제외됩니다.
  2. 생태계 호환성: 새로운 키워드를 도입하면 생태계 전반에 걸쳐 파서 마이그레이션이 필요합니다. Python의 타입 시스템은 타입의 산술 체계로 발전하고 있으므로(예: Union을 |로 대체), 연산자를 재사용하면 불필요한 변경 없이 이 패턴을 확장할 수 있습니다.
  3. 의미의 명확성: 선택한 문법은 원시 타입에 대해 확립된 직관과 충돌하지 않아야 합니다.

그러면 &보다 강하게 결합하는 재정의 가능한 이항 연산자 집합이 남습니다: **, *, @, /, //, %, +, -, >>, <<.

+, -, /, //, *, **, % 같은 표준 산술 연산자는 오해를 불러일으킵니다. int + x 또는 float / Field()를 읽으면 메타데이터 장식이 아니라 수학적 평가를 강하게 연상하게 됩니다.

남은 후보는 <<, >>, @입니다. @를 선택한 이유는 Python에서 데코레이터를 통해 이미 메타데이터와 연관되어 있기 때문입니다. NumPy 같은 라이브러리는 이를 행렬 곱셈에 사용하지만, +나 / 같은 연산자만큼 산술 연산에 밀접하게 연결되어 있지는 않습니다.

하위 호환성

상위 10,000개 PyPI 프로젝트를 분석한 결과, __matmul__ 또는 __rmatmul__에 대한 메타클래스 오버로드는 0건이었습니다. (비교하자면, 이 분석에서는 pgpy (#1,690), dataclass-wizard (#2,205), multimethod (#2,377) 같은 라이브러리에서 드물게 __or__와 __and__ 오버로드가 발견되었습니다).

순수 Python으로 구현된 typing._AnnotatedAlias 클래스는 네이티브 C 구현(types.AnnotatedType)으로 대체됩니다. typing.Annotated는 사용자 정의 메타클래스를 가진 특수 형식이 아니라 이 C 타입에 대한 참조가 됩니다.

원활한 전환을 보장하기 위해, 이 레거시 클래스는 폐지 예정인 호환성 심(shim)으로 유지됩니다. isinstance(x, typing._AnnotatedAlias)를 사용하는 코드는 계속 동작하지만 DeprecationWarning을 발생시킵니다. 이 심은 Python 3.21에서 제거될 예정입니다(Open Issues 참조).

수정해야 하는 코드:

  • type(ann).__name__ == '_AnnotatedAlias' → isinstance(ann, types.AnnotatedType) 또는 typing.get_origin(ann) is Annotated를 사용하십시오.
  • typing._AnnotatedAlias(origin, metadata) → Annotated[origin, *metadata] 또는 origin @m1 @m2를 사용하십시오.

typing_extensions를 통한 백포팅: X | Y와 마찬가지로, @ 축약 표기는 메타타입(type.__matmul__)을 변경해야 하며, 이는 순수 Python에서 패치할 수 없습니다. 이 축약 표기는 Python 3.16 이상에서만 사용할 수 있습니다. 기존 Annotated[X, Y] 문법은 지원되는 모든 버전에서 계속 동작하며, 하위 호환성이 필요한 경우에는 이 문법을 사용해야 합니다.

보안 영향

직접적인 보안 영향은 없습니다.

교육 방법

Python에서 @ 기호는 이미 데코레이터를 통해 메타데이터와 확립된 연관성을 가지고 있습니다. 어노테이션 축약 표기는 이 직관을 타입 시스템으로 확장합니다. int @ Field(gt=0)는 “Field(gt=0)로 데코레이트된 int”로 읽힙니다.

초보자를 위한 핵심 규칙은 다음과 같습니다. 타입 어노테이션에서 ``@``는 “이 메타데이터와 함께”를 의미합니다. 숙련된 개발자에게는 이 멘탈 모델이 Python의 표준 연산자 우선순위에 그대로 대응합니다(@는 |보다 더 강하게 결합합니다).

문서와 교육 자료에서는 이 축약 표기를 메타데이터를 적용하는 주된 문법으로 소개해야 합니다. 장황한 typing.Annotated 형식은 주로 라이브러리 작성자와 관련되거나 타입을 동적으로 생성할 때 필요한 고급 세부 사항으로 다루어야 합니다.

사용 예시

Pydantic 유효성 검사: 이 축약 표기는 데이터 유효성 검사 시나리오에서 사용할 수 있습니다:

from pydantic import BaseModel, Field, HttpUrl
from annotated_types import Len

class Project(BaseModel):
    name: str @ Field(title="Project Name") @ Len(1)
    url: HttpUrl @ Field(description="The project homepage")
    stars: int @ Field(ge=0) = 0

FastAPI 의존성 주입: FastAPI에서 이 축약 표기는 복잡한 매개변수 정의를 단순화합니다:

from fastapi import FastAPI, Header, Depends

app = FastAPI()

@app.get("/secure")
async def secure_endpoint(token: str @ Header(description="Auth token")):
    return {"status": "authorized"}

SQLModel과 데이터베이스 정의: SQLModel은 열 속성을 정의하기 위해 Annotated에 의존합니다. 축약 문법은 이러한 정의를 깔끔하게 정리합니다:

from sqlmodel import SQLModel, Field

class Hero(SQLModel, table=True):
    id: (int | None) @ Field(primary_key=True) = None
    name: str @ Field(index=True)
    secret_name: str
    age: (int | None) @ Field(index=True) = None

테스트와 형식 검증: Hypothesis나 CrossHair 같은 라이브러리는 테스트 생성을 제약하기 위해 annotated-types를 사용합니다. 이 축약 표기는 테스트 경계를 지정하기 위한 깔끔한 문법을 제공합니다:

from dataclasses import dataclass
from annotated_types import Ge, Interval
from hypothesis import given

@dataclass
class InventoryItem:
    # A non-negative quantity
    quantity: int @ Ge(0)
    # A price bounded between 1 and 100
    price: float @ Interval(gt=0, le=100)

@given(...)
def test_inventory(item: InventoryItem):
    assert item.price * item.quantity >= 0

생태계 마이그레이션

여러 주요 타입 기반 프레임워크를 이 축약 표기로 마이그레이션하여 검증했습니다. 테스트가 계속 통과하도록 두 단계로 진행했습니다.

  1. 지원 추가: 먼저 라이브러리의 내부 메커니즘이 @ 문법을 받아들이도록 갱신했습니다.
  2. 프로젝트 이전: 그다음 Annotated의 모든 내부 사용을 @ 연산자로 이전했습니다.

지원을 추가하는 데 필요했던 수정량은 다음과 같습니다.

  • FastAPI: 0 LOC (Pydantic에서 상속)
  • Pydantic: 50 LOC
  • SQLAlchemy: 282 LOC
  • cattrs: 155 LOC
  • Hypothesis: 15 LOC
  • Beartype: 12 LOC

하위 호환성을 유지하기 위해 이러한 변경 대부분은 테스트 스위트와 문자열 표현 로직(__repr__ 등)에만 국한되었습니다. 합계에는 라이브러리의 메타데이터 베이스 클래스에 두 줄짜리 __rmatmul__ 메서드를 추가한 것도 포함되어 있습니다. 이 메서드는 해석할 수 없는 전방 참조와 None @ Metadata를 지원하기 위한 대체 수단 역할을 합니다.

참조 구현

다음 도구에 대한 프로토타입 구현을 사용할 수 있습니다.

타입 검사기를 지원하기 위해 Typeshed의 builtins.type에 def __matmul__(self, other: Any) -> types.AnnotatedType: ...가 추가될 것입니다.

기각된 아이디어

대안 문법

@의 재사용을 둘러싼 논의에서 여러 대안이 나왔습니다.

  • 새로운 중위 연산자: int <@ Field(...)
  • 새로운 소프트 키워드: int annotated Interval(1, 10)
  • 중괄호 문법: x: int {Gt(10), Lt(20)}

이 대안들은 합의를 얻지 못했습니다. 또한 나중에 언어가 심볼 데코레이터를 도입하기로 한다면 @ 기호는 그쪽으로도 깔끔하게 확장됩니다.

__rmatmul__에만 의존하는 방식

메타데이터 객체가 (예를 들어 베이스 클래스를 통해) __rmatmul__을 구현하여 Annotated 타입을 반환하는 방식에 전적으로 의존하는 것은 명시적으로 기각합니다:

class Metadata[T = object]:
    def __rmatmul__(self, typ: TypeForm[T], /) -> TypeForm[T]:
        return Annotated[typ, self]

class Le(Metadata[int]):
    ...

이 접근 방식은 두 가지 이유로 거부되었습니다. 첫째, __rmatmul__을 구현하는 일을 임의의 우변 객체에 의존하면 타입 표현식 모델이 깨지고, 연산자에 고정된 정적 의미를 부여하기가 복잡해집니다. 이는 | 연산자가 작동하는 방식과 일치하지 않으며, 명세에 정의된 타입 표현식 문법과도 모순됩니다. 둘째, PEP 593은 유효한 모든 Python 객체(예: 문자열, 딕셔너리)를 메타데이터로 허용한다고 명시하고 있습니다. type 메타클래스에 __matmul__을 직접 구현하면 옵트인 베이스 클래스가 필요 없고 일관된 동작을 제공합니다.

매개변수/필드 데코레이터

진정한 매개변수/필드 데코레이터는 당분간 거부되었습니다. 심볼 수준 데코레이터를 위해 핵심 파서를 수정하면 복잡한 설계 공간이 열리므로, 이 PEP는 @ 연산자의 범위를 타입 표현식으로 한정합니다.

행렬 곱셈 때문에 @ 사용을 피하는 방안

행렬 곱셈과 연관된다는 이유로 @를 피해야 한다는 인자는 받아들이지 않았습니다. 타입 표현식 안에서 Python은 이미 표준 연산자(유니언을 위한 |, 제네릭을 위한 [] 등)를 타이핑 고유의 의미로 재사용하고 있습니다(Why the @ Operator? 참고).

구조적 평가 형식(Format.TYPE)

초기 초안에서는 @ 연산자를 구조적으로 평가하고 해석할 수 없는 ForwardRef 인스턴스에 메타데이터를 보존하기 위해 annotationlib (PEP 749)에 새로운 Format.TYPE을 도입할 것을 제안했습니다.

이 제안은 거부되었습니다. 프로토타입 통합(beartype, pydantic, fastapi, sqlalchemy, hypothesis) 결과, 기존 생태계가 annotationlib의 현재 도구를 사용하여 @ 연산자를 처리한다는 사실이 확인되었습니다.

공백 없는 서식(@annot)

처음에는 함수 데코레이터와 시각적으로 유사하게 보이도록 축약 표기를 공백 없이 작성하는 서식(예: int @Field)을 검토했습니다. 이 방식은 포매터(Black, Ruff 등)가 @ 연산자에 대해 복잡하고 문맥에 따라 달라지는 규칙을 유지하도록 강제하기 때문에 거부되었습니다.

향후 작업

이 제안은 그 자체로 독립적이지만, 타입 메타데이터용으로 @ 연산자를 확립하면 향후 여러 확장이 가능해집니다.

네이티브 심볼 데코레이터

개발자들은 프레임워크에 구애받지 않는 필드 데코레이터를 요청합니다:

class User(BaseModel):
    @Field(primary_key=True)
    id: int

향후 제안은 두 가지 아키텍처 모델 사이에서 방향을 정해야 할 가능성이 높습니다:

  • 디스크립터 모델: 데코레이터가 디스크립터를 반환하는 런타임 함수로 동작하여 속성 접근을 능동적으로 가로챕니다(표준 Python 함수 데코레이터와 유사합니다).
  • 메타데이터 모델: 필드 구성을 수동적인 심볼 메타데이터로 취급하며, 이를 둘러싼 클래스가 생성 시점에 처리하도록 맡깁니다.

이 제안은 메타데이터 모델에 부합합니다. 타입 기반 라이브러리는 일반적으로 독립적인 디스크립터에 의존하기보다는 클래스 생성 중에 정적 정의를 검사합니다.

@Field(...) id: int 구문은 id: int @Field(...) 구문과 동일하게 평가될 것입니다. 이를 통해 기존 프레임워크는 필드 구성을 검사할 수 있고 수정 없이 계속 동작할 수 있습니다. 이 모델에서 값 공간 데코레이터는 런타임에 객체를 수정하고, 타입 공간 데코레이터는 타입에 메타데이터를 부착합니다.

대상 지정 메타데이터

향후 PEP 746을 확장하면 어노테이션 대상(annotation target)을 지원할 수 있습니다. 베이스 타입과 명시적인 대상 제약 조건을 교차(intersection)하면, 타입 검사기가 메타데이터가 어디에 존재할 수 있는지를 검증하게 됩니다. 이를 통해 잘못된 사용(예: 클래스 필드가 아닌 함수 매개변수에 @Column을 붙이는 경우)을 줄일 수 있습니다:

(참고: 다음 예제는 교차 타입을 위한 ``&`` 연산자가 추가되었다고 가정합니다.)

from typing import Target

class Column:
    """Valid only on integers that are fields of a SQLAlchemy Model."""
    __supports_annotated_base__: int & Target.FIELD[SQLAlchemy.Model]

메타데이터를 위한 네이티브 문법을 확립하면 어노테이션 대상과 같은 향후 확장을 위한 구조적 토대가 마련됩니다.

미해결 문제

폐지 일정: typing._AnnotatedAlias는 비공개 클래스이므로 표준 5년 폐지 정책(PEP 387)을 우회할 수도 있습니다. 이 클래스의 제거를 앞당겨야 합니까?

감사의 글

이 제안을 다듬는 과정에서 피드백과 조언, 도움을 주신 Hugo van Kemenade, Jelle Zijlstra, Eric Traut께 감사드립니다.

참고 문헌