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

Python 개선 제안 한국어 번역

PEP 526 – 변수 어노테이션 문법

Author:
Ryan Gonzalez <rymg19 at gmail.com>, Philip House <phouse512 at gmail.com>, Ivan Levkivskyi <levkivskyi at gmail.com>, Lisa Roach <lisaroach14 at gmail.com>, Guido van Rossum <guido at python.org>
Status:
Final
Type:
Standards Track
Topic:
Typing
Created:
09-Aug-2016
Python-Version:
3.6
Post-History:
30-Aug-2016, 02-Sep-2016
Resolution:
Python-Dev message

Table of Contents

번역·라이선스 안내

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

Important

This PEP is a historical document: see Annotated assignment statements, ClassVar 및 typing.ClassVar for up-to-date specs and documentation. Canonical typing specs are maintained at the typing specs site; runtime typing behaviour is described in the CPython documentation.

×

See the typing specification update process for how to propose changes to the typing spec.

상태

이 PEP는 BDFL에 의해 잠정적으로 승인되었습니다. 자세한 내용은 승인 메시지를 참고하십시오: https://mail.python.org/pipermail/python-dev/2016-September/146282.html

검토자를 위한 공지

이 PEP는 별도의 저장소에서 초안이 작성되었습니다: https://github.com/phouse512/peps/tree/pep-0526.

python-ideas와 https://github.com/python/typing/issues/258 에서 사전 논의가 있었습니다.

공개 포럼에서 반대 의견을 제기하기 전에, 적어도 이 PEP의 끝부분에 나열된 기각된 아이디어의 요약은 읽어 주십시오.

초록

PEP 484는 타입 힌트, 즉 타입 어노테이션을 도입했습니다. 주된 초점은 함수 어노테이션이었지만, 변수에 어노테이션을 달기 위한 타입 주석이라는 개념도 도입했습니다:

# 'primes' is a list of integers
primes = []  # type: List[int]

# 'captain' is a string (Note: initial value is a problem)
captain = ...  # type: str

class Starship:
    # 'stats' is a class variable
    stats = {}  # type: Dict[str, int]

이 PEP는 변수(클래스 변수와 인스턴스 변수 포함)의 타입을 주석으로 표현하는 대신 어노테이션으로 나타낼 수 있는 문법을 Python에 추가하는 것을 목표로 합니다:

primes: List[int] = []

captain: str  # Note: no initial value!

class Starship:
    stats: ClassVar[Dict[str, int]] = {}

PEP 484는 타입 주석이 복잡한 경우의 타입 추론을 돕기 위한 것이라고 명시하고 있으며, 이 PEP는 이러한 의도를 바꾸지 않습니다. 그러나 실제로는 타입 주석이 클래스 변수와 인스턴스 변수에도 사용되어 왔으므로, 이 PEP에서는 그러한 변수에 타입 어노테이션을 사용하는 방법도 다룹니다.

근거

타입 주석은 충분히 잘 동작하지만, 주석을 통해 표현된다는 점에는 몇 가지 단점이 있습니다:

  • 텍스트 편집기는 흔히 주석을 타입 어노테이션과 다르게 강조 표시합니다.
  • 정의되지 않은 변수의 타입에 어노테이션을 달 방법이 없으며, 변수를 None으로 초기화해야 합니다(예: a = None # type: int).
  • 조건 브랜치에서 어노테이션된 변수는 읽기 어렵습니다:
    if some_value:
        my_var = function() # type: Logger
    else:
        my_var = another_function() # Why isn't there a type here?
    
  • 타입 주석은 실제로 언어의 일부가 아니므로, Python 스크립트가 이를 파싱하려면 ast를 사용하는 것만으로는 부족하고 별도의 파서가 필요합니다.
  • typeshed에서는 타입 주석이 많이 사용됩니다. typeshed가 타입 주석 대신 변수 어노테이션 문법을 사용하도록 마이그레이션하면 스텁의 가독성이 향상될 것입니다.
  • 일반 주석과 타입 주석이 함께 사용되는 상황에서는 둘을 구별하기 어렵습니다:
    path = None  # type: Optional[str]  # Path to module source
    
  • 모듈의 소스 코드를 찾아 런타임에 파싱하는 방법 외에는 런타임에 어노테이션을 얻어 낼 방법이 없으며, 이는 좋게 말해도 우아하지 않습니다.

이러한 문제의 대부분은 해당 문법을 언어의 핵심 부분으로 만들면 완화될 수 있습니다. 또한 (메서드 어노테이션에 더해) 클래스 변수와 인스턴스 변수를 위한 전용 어노테이션 문법이 있으면, PEP 484에서 정의한 명목적 타이핑을 보완하는 정적 덕 타이핑으로 나아가는 길이 열릴 것입니다.

목표가 아닌 것

이 제안에는 런타임에 어노테이션을 얻기 위한 typing.get_type_hints 표준 라이브러리 함수의 확장이 수반되지만, 변수 어노테이션은 런타임 타입 검사를 위해 설계된 것이 아닙니다. 그러한 기능을 구현하려면 서드파티 패키지가 개발되어야 할 것입니다.

또한 Python은 여전히 동적 타입 언어로 남을 것이며, 저자들은 관례로라도 타입 힌트를 필수로 만들 생각이 전혀 없다는 점을 강조해야 합니다. 타입 어노테이션을 정적 타입 언어의 변수 선언과 혼동해서는 안 됩니다. 어노테이션 문법의 목표는 서드파티 도구를 위해 구조화된 타입 메타데이터를 쉽게 지정할 수 있는 방법을 제공하는 것입니다.

이 PEP는 타입 검사기에 타입 검사 규칙을 변경하도록 요구하지 않습니다. 이 PEP는 타입 주석(type comment)을 대체할 더 읽기 쉬운 문법을 제공할 뿐입니다.

명세

할당문이나 단일 표현식에 타입 어노테이션을 추가하여 서드파티 타입 검사기에 어노테이션 대상의 의도된 타입을 나타낼 수 있습니다:

my_var: int
my_var = 5  # Passes type check.
other_var: int  = 'a'  # Flagged as error by type checker,
                       # but OK at runtime.

이 문법은 PEP 484에 없는 새로운 의미를 도입하지 않으므로 다음 세 문장은 동등합니다:

var = value # type: annotation
var: annotation; var = value
var: annotation = value

아래에서는 여러 문맥에서 사용하는 타입 어노테이션의 문법과 그 런타임 효과를 명세합니다.

타입 검사기가 어노테이션을 어떻게 해석할 수 있는지도 제안하지만, 이 제안을 반드시 따라야 하는 것은 아닙니다. (이는 PEP 484에서 준수에 대해 취하는 태도와 같습니다.)

전역 변수와 지역 변수 어노테이션

지역 변수와 전역 변수의 타입은 다음과 같이 어노테이션할 수 있습니다:

some_number: int           # variable without initial value
some_list: List[int] = []  # variable with initial value

초기값을 생략할 수 있으면 조건 브랜치에서 대입되는 변수에 타입을 더 쉽게 지정할 수 있습니다:

sane_world: bool
if 2+2 == 4:
    sane_world = True
else:
    sane_world = False

이 문법은 튜플 패킹은 허용하지만, 튜플 언패킹을 사용할 때 변수의 타입을 어노테이션하는 것은 허용하지 않는다는 점에 유의하십시오:

# Tuple packing with variable annotation syntax
t: Tuple[int, ...] = (1, 2, 3)
# or
t: Tuple[int, ...] = 1, 2, 3  # This only works in Python 3.8+

# Tuple unpacking with variable annotation syntax
header: str
kind: int
body: Optional[List[str]]
header, kind, body = message

초깃값을 생략하면 변수는 초기화되지 않은 상태로 남습니다:

a: int
print(a)  # raises NameError

그러나 지역 변수에 어노테이션을 달면 인터프리터는 항상 그 변수를 지역 변수로 만듭니다:

def f():
    a: int
    print(a)  # raises UnboundLocalError
    # Commenting out the a: int makes it a NameError.

마치 코드가 다음과 같은 것처럼 동작합니다:

def f():
    if False: a = 0
    print(a)  # raises UnboundLocalError

중복된 타입 어노테이션은 무시됩니다. 그러나 정적 타입 검사기는 같은 변수에 다른 타입으로 어노테이션을 달면 경고를 낼 수 있습니다:

a: int
a: str  # Static type checker may or may not warn about this.

클래스 변수와 인스턴스 변수 어노테이션

타입 어노테이션은 클래스 본문과 메서드에서 클래스 변수와 인스턴스 변수를 어노테이션하는 데에도 사용할 수 있습니다. 특히 값이 없는 표기법 a: int를 사용하면 __init__ 또는 __new__에서 초기화되어야 하는 인스턴스 변수를 어노테이션할 수 있습니다. 제안하는 문법은 다음과 같습니다:

class BasicStarship:
    captain: str = 'Picard'               # instance variable with default
    damage: int                           # instance variable without default
    stats: ClassVar[Dict[str, int]] = {}  # class variable

여기서 ClassVar는 typing 모듈에 정의된 특별한 클래스로, 이 변수를 인스턴스에 설정해서는 안 된다는 것을 정적 타입 검사기에 알립니다.

중첩 수준과 관계없이 ClassVar 매개변수에는 어떤 타입 변수도 포함될 수 없다는 점에 유의하십시오: T가 타입 변수라면 ClassVar[T]와 ClassVar[List[Set[T]]]는 모두 유효하지 않습니다.

더 자세한 예제로 이를 설명할 수 있습니다. 다음 클래스에서:

class Starship:
    captain = 'Picard'
    stats = {}

    def __init__(self, damage, captain=None):
        self.damage = damage
        if captain:
            self.captain = captain  # Else keep the default

    def hit(self):
        Starship.stats['hits'] = Starship.stats.get('hits', 0) + 1

stats는 (여러 가지 게임별 통계를 추적하는) 클래스 변수로 의도된 반면, captain은 클래스에서 기본값이 설정된 인스턴스 변수입니다. 타입 검사기는 이 차이를 알아채지 못할 수 있습니다: 둘 다 클래스에서 초기화되지만, captain은 인스턴스 변수의 편리한 기본값 역할만 할 뿐이고, stats는 진정한 클래스 변수로서 모든 인스턴스가 공유하도록 의도된 것입니다.

두 변수가 모두 클래스 수준에서 초기화되므로, 클래스 변수를 ClassVar[...]로 감싼 타입으로 어노테이션하여 표시해 두면 둘을 구분하는 데 유용합니다. 이렇게 하면 타입 검사기가 인스턴스에서 같은 이름의 속성에 실수로 대입하는 것을 플래그로 표시할 수 있습니다.

예를 들어, 앞서 논의한 클래스를 어노테이션하면 다음과 같습니다:

class Starship:
    captain: str = 'Picard'
    damage: int
    stats: ClassVar[Dict[str, int]] = {}

    def __init__(self, damage: int, captain: str = None):
        self.damage = damage
        if captain:
            self.captain = captain  # Else keep the default

    def hit(self):
        Starship.stats['hits'] = Starship.stats.get('hits', 0) + 1

enterprise_d = Starship(3000)
enterprise_d.stats = {} # Flagged as error by a type checker
Starship.stats = {} # This is OK

편의상(그리고 관례상), 인스턴스 변수는 클래스가 아니라 __init__ 또는 다른 메서드에서 어노테이션할 수 있습니다:

from typing import Generic, TypeVar
T = TypeVar('T')

class Box(Generic[T]):
    def __init__(self, content):
        self.content: T = content

표현식 어노테이션

어노테이션 대상은 적어도 문법적으로는 유효한 단일 할당 대상이면 무엇이든 될 수 있습니다(이를 어떻게 처리할지는 타입 검사기가 결정합니다):

class Cls:
    pass

c = Cls()
c.x: int = 0  # Annotates c.x with int.
c.y: int      # Annotates c.y with int.

d = {}
d['a']: int = 0  # Annotates d['a'] with int.
d['b']: int      # Annotates d['b'] with int.

괄호로 감싼 이름도 단순한 이름이 아니라 표현식으로 간주된다는 점에 유의하십시오:

(x): int      # Annotates x with int, (x) treated as expression by compiler.
(y): int = 0  # Same situation here.

어노테이션이 허용되지 않는 곳

같은 함수 스코프에서 global이나 nonlocal의 대상이 되는 변수에 어노테이션을 달려고 시도하는 것은 허용되지 않습니다:

def f():
    global x: int  # SyntaxError

def g():
    x: int  # Also a SyntaxError
    global x

그 이유는 global과 nonlocal이 변수를 소유하지 않기 때문입니다. 따라서 타입 어노테이션은 해당 변수를 소유하는 스코프에 속합니다.

단일 할당 대상과 단일 우변 값만 허용됩니다. 또한 for 문이나 with 문에서 사용되는 변수에는 어노테이션을 달 수 없습니다. 이러한 변수에는 튜플 언패킹과 비슷한 방식으로 미리 어노테이션을 달 수 있습니다:

a: int
for a in my_iter:
    ...

f: MyFile
with myfunc() as f:
    ...

스텁 파일에서의 변수 어노테이션

변수 어노테이션은 타입 주석보다 가독성이 좋으므로, Python 2.7을 포함한 모든 Python 버전의 스텁 파일에서 변수 어노테이션을 사용하는 것이 권장됩니다. 스텁 파일은 Python 인터프리터가 실행하지 않으므로 변수 어노테이션을 사용해도 오류가 발생하지 않는다는 점에 유의하십시오. 타입 검사기는 모든 Python 버전의 스텁에서 변수 어노테이션을 지원해야 합니다. 예를 들면 다음과 같습니다:

# file lib.pyi

ADDRESS: unicode = ...

class Error:
    cause: Union[str, unicode]

변수 어노테이션에 권장되는 코딩 스타일

모듈 수준 변수, 클래스 변수와 인스턴스 변수, 지역 변수에 대한 어노테이션은 해당 콜론 뒤에 공백을 한 칸 두어야 합니다. 콜론 앞에는 공백이 없어야 합니다. 대입에 우변이 있다면, 동등 기호(=) 양쪽에는 정확히 공백 하나씩 있어야 합니다. 예시:

  • 올바른 예:
    code: int
    
    class Point:
        coords: Tuple[int, int]
        label: str = '<unknown>'
    
  • 잘못된 예:
    code:int  # No space after colon
    code : int  # Space before colon
    
    class Test:
        result: int=0  # No spaces around equality sign
    

표준 라이브러리와 문서의 변경 사항

  • typing 모듈에 새로운 공변 타입 ClassVar[T_co]가 추가됩니다. 이 타입은 유효한 타입이어야 하는 단일 인자만 받으며, 클래스 인스턴스에 설정되어서는 안 되는 클래스 변수에 어노테이션을 다는 데 사용됩니다. 이 제약은 정적 검사기가 보장하지만, 런타임에는 보장되지 않습니다. ClassVar 사용법에 대한 예시와 설명은 classvar 절을 참고하시고, ClassVar의 도입 근거에 대한 자세한 내용은 rejected 절을 참고하십시오.
  • typing 모듈의 get_type_hints 함수가 확장되어, 함수뿐만 아니라 모듈과 클래스에서도 런타임에 타입 어노테이션을 가져올 수 있게 됩니다. 어노테이션은 변수나 인자를 각각의 타입 힌트로 매핑하는 딕셔너리로 반환되며, 이때 전방 참조는 평가된 상태입니다. 클래스의 경우에는 메서드 결정 순서에 따라 어노테이션으로부터 구성한 매핑(아마도 collections.ChainMap)을 반환합니다.
  • 어노테이션 사용에 관한 권장 지침이 문서에 추가될 예정이며, 여기에는 이 PEP와 PEP 484에서 설명한 명세를 교육적으로 정리한 내용이 담깁니다. 또한 타입 주석을 타입 어노테이션으로 변환하는 보조 스크립트가 표준 라이브러리와 별도로 공개될 예정입니다.

타입 어노테이션의 런타임 효과

지역 변수에 어노테이션을 달면, 해당 변수에 한 번도 값이 할당되지 않았더라도 인터프리터는 그 변수를 지역 변수로 취급합니다. 지역 변수에 대한 어노테이션은 평가되지 않습니다:

def f():
    x: NonexistentName  # No error.

그러나 모듈 수준이나 클래스 수준에서는 그 타입이 실제로 평가됩니다:

x: NonexistentName  # Error!
class X:
    var: NonexistentName  # Error!

또한 모듈이나 클래스 수준에서 어노테이션 대상이 단순 이름이면, 그 이름과 어노테이션은 해당 모듈이나 클래스의 __annotations__ 속성에 이름에서 평가된 어노테이션으로의 순서 있는 매핑으로 저장됩니다(비공개 이름이면 맹글링됩니다). 다음은 예시입니다:

from typing import Dict
class Player:
    ...
players: Dict[str, Player]
__points: int

print(__annotations__)
# prints: {'players': typing.Dict[str, __main__.Player],
#          '_Player__points': <class 'int'>}

__annotations__는 쓰기 가능하므로 다음이 허용됩니다:

__annotations__['s'] = str

하지만 __annotations__를 순서 있는 매핑이 아닌 다른 것으로 갱신하려고 하면 TypeError가 발생할 수 있습니다:

class C:
    __annotations__ = 42
    x: int = 5  # raises TypeError

(문제의 원인인 __annotations__에 대한 대입은 Python 인터프리터가 아무 의문 없이 받아들인다는 점에 유의하십시오 – 하지만 뒤따르는 타입 어노테이션은 그것이 MutableMapping이기를 기대하므로 실패합니다.)

런타임에 어노테이션을 얻는 권장 방법은 typing.get_type_hints 함수를 사용하는 것입니다. 모든 던더 속성과 마찬가지로, 문서화되지 않은 방식으로 __annotations__를 사용하면 예고 없이 동작하지 않게 될 수 있습니다:

from typing import Dict, ClassVar, get_type_hints
class Starship:
    hitpoints: int = 50
    stats: ClassVar[Dict[str, int]] = {}
    shield: int = 100
    captain: str
    def __init__(self, captain: str) -> None:
        ...

assert get_type_hints(Starship) == {'hitpoints': int,
                                    'stats': ClassVar[Dict[str, int]],
                                    'shield': int,
                                    'captain': str}

assert get_type_hints(Starship.__init__) == {'captain': str,
                                             'return': None}

어노테이션이 정적으로 발견되지 않으면 __annotations__ 딕셔너리는 아예 생성되지 않는다는 점에 유의하십시오. 또한 어노테이션을 지역적으로 사용할 수 있다는 이점이 함수 호출마다 어노테이션 딕셔너리를 생성하고 채워야 하는 비용을 상쇄하지 못합니다. 따라서 함수 수준의 어노테이션은 평가되지도 저장되지도 않습니다.

어노테이션의 다른 용도

이 PEP를 적용한 Python이 다음 코드에 이의를 제기하는 객체가 되지는 않지만:

alice: 'well done' = 'A+'
bob: 'what a shame' = 'F-'

“예외를 발생시키지 않고 평가된다”는 것 이상으로는 타입 어노테이션에 관여하지 않기 때문입니다. 하지만 이를 만난 타입 검사기는 # type: ignore나 @no_type_check로 비활성화하지 않는 한 이를 플래그로 표시합니다.

그러나 Python은 “타입”이 무엇인지 신경 쓰지 않으므로, 위 코드 조각이 전역 수준이나 클래스 안에 있다면 __annotations__에는 {'alice': 'well done', 'bob': 'what a shame'}가 포함됩니다.

이렇게 저장된 어노테이션은 다른 용도로 사용될 수도 있지만, 이 PEP에서는 어노테이션의 바람직한 용도로 타입 힌트를 명시적으로 권장합니다.

기각/연기된 제안

  • 변수 어노테이션을 도입해야 하는가? 변수 어노테이션은 PEP 484가 승인한 타입 주석의 형태로 이미 2년 가까이 존재해 왔습니다. 변수 어노테이션은 서드파티 타입 검사기(mypy, pytype, PyCharm 등)와 타입 검사기를 사용하는 프로젝트에서 널리 사용되고 있습니다. 하지만 주석 문법에는 근거(Rationale) 절에서 열거한 여러 단점이 있습니다. 이 PEP는 타입 어노테이션의 필요성에 관한 것이 아니라, 그러한 어노테이션의 문법이 어떠해야 하는지에 관한 것입니다.
  • 새 키워드 도입: 좋은 키워드를 고르기는 어렵습니다. 예를 들어 var는 너무 흔한 변수 이름이므로 쓸 수 없고, 클래스 변수나 전역 변수에도 사용하려면 local도 쓸 수 없습니다. 둘째로, 무엇을 선택하든 여전히 __future__ 임포트가 필요합니다.
  • 키워드로 def 사용: 제안은 다음과 같습니다:
    def primes: List[int] = []
    def captain: str
    

    이 방식의 문제는 여러 세대의 Python 프로그래머(와 도구!)에게 def가 “함수를 정의한다”는 뜻이라는 점이며, 이를 변수 정의에도 사용하면 명확성이 높아지지 않습니다. (물론 이는 주관적인 판단입니다.)

  • 함수 기반 문법 사용: var = cast(annotation[, value])를 사용하여 변수의 타입을 어노테이션하자는 제안이 있었습니다. 이 문법은 AST에 어노테이션이 없다는 점과 같은 타입 주석의 일부 문제를 완화하지만, 가독성 같은 다른 문제는 해결하지 못하며 런타임 오버헤드를 일으킬 수 있습니다.
  • 튜플 언패킹에 타입 어노테이션 허용: 이는 모호함을 낳습니다. 다음 문장이 무엇을 의미하는지 명확하지 않습니다:
    x, y: T
    

    x와 y가 모두 T 타입입니까, 아니면 T가 x와 y에 나누어 할당되는 두 항목의 튜플 타입이기를 기대하는 것입니까, 혹은 x의 타입이 Any이고 y의 타입이 T인 것입니까? (이것이 함수 시그니처에 나타났다면 마지막 해석이 그 의미가 됩니다.) (사람인) 독자가 추측하도록 내버려 두기보다는, 적어도 지금은 이를 금지합니다.

  • 어노테이션용 괄호 형식 (var: type): 위에서 언급한 모호함에 대한 해결책으로 python-ideas에서 제기되었지만, 그러한 문법은 복잡하고 이점은 적으며 가독성이 떨어지므로 기각되었습니다.
  • 연쇄 할당에서 어노테이션 허용: 이는 튜플 언패킹과 비슷한 모호함과 가독성 문제를 안고 있습니다. 예를 들어 다음에서:
    x: int = y = 1
    z = w: int = 1
    

    y와 z의 타입이 무엇이어야 하는지 모호합니다. 또한 두 번째 줄은 파싱하기 어렵습니다.

  • 어노테이션을 with 문과 for 문에서 허용: 이 제안은 기각되었습니다. for에서는 실제 이터러블을 알아보기 어려워지고, with에서는 CPython의 LL(1) 파서를 혼란스럽게 하기 때문입니다.
  • 지역 어노테이션을 함수 정의 시점에 평가하기: 이 제안은 Guido가 거부했습니다. 어노테이션의 위치로 볼 때 어노테이션이 주변 코드와 같은 스코프에 있다고 강하게 해석되기 때문입니다.
  • 변수 어노테이션을 함수 스코프에도 저장하기: 어노테이션을 지역적으로 사용할 수 있다는 가치는 매 함수 호출마다 딕셔너리를 생성하고 채우는 비용을 상당 부분 상쇄할 만큼 크지 않습니다.
  • 대입 없이 어노테이션된 변수 초기화하기: python-ideas에서 x: int의 x를 None 또는 Javascript의 undefined 같은 별도의 특수 상수로 초기화하자는 제안이 있었습니다. 그러나 언어에 또 다른 싱글턴 값을 추가하면 코드의 모든 곳에서 그 값을 검사해야 합니다. 따라서 Guido는 이 제안에 딱 잘라 “No”라고 답했습니다.
  • typing 모듈에 InstanceVar도 추가하기: 인스턴스 변수는 클래스 변수보다 훨씬 더 흔하므로 이는 불필요합니다. 더 흔한 용법이 기본값이 되는 것이 마땅합니다.
  • 인스턴스 변수 어노테이션을 메서드 안에서만 허용하기: 문제는 많은 __init__ 메서드가 인스턴스 변수 초기화 외에도 많은 일을 하므로, (사람이) 모든 인스턴스 변수 어노테이션을 찾기가 더 어려워진다는 점입니다. 또한 때로는 __init__이 여러 보조 메서드로 나뉘어 있어서 어노테이션을 추적하기가 더욱 어렵습니다. 인스턴스 변수 어노테이션을 클래스 안에 모아 두면 찾기가 더 쉬워지고, 코드를 처음 읽는 사람에게도 도움이 됩니다.
  • 클래스 변수에 x: class t = v 문법 사용: 이 방식은 더 복잡한 파서가 필요하고, class 키워드가 단순한 문법 하이라이터를 혼란스럽게 할 것입니다. 어차피 ClassVar가 클래스 변수를 __annotations__에 저장하도록 해야 하므로, 더 단순한 문법을 선택했습니다.
  • 아예 ClassVar를 잊어버리기: mypy가 클래스 변수와 인스턴스 변수를 구분하는 방법 없이도 잘 동작하는 것으로 보였기 때문에 이 방안이 제안되었습니다. 그러나 타입 검사기는 추가 정보로 유용한 일을 할 수 있습니다. 예를 들어 인스턴스를 통해 클래스 변수에 실수로 대입하는 것(이렇게 하면 클래스 변수를 가리는 인스턴스 변수가 생성됩니다)을 플래그로 표시할 수 있습니다. 또한 잘 알려진 위험 요소인, 변경 가능한 기본값을 가진 인스턴스 변수를 플래그로 표시할 수도 있습니다.
  • ClassVar 대신 ClassAttr 사용: ClassVar가 더 나은 주된 이유는 다음과 같습니다. 메서드, 디스크립터 등 많은 것이 클래스 속성입니다. 하지만 개념적으로 클래스 변수(또는 상수)인 것은 특정 속성뿐입니다.
  • 어노테이션을 평가하지 않고 문자열로 취급하기: 이는 항상 평가되는 함수 어노테이션의 동작과 일관되지 않습니다. 향후 재검토될 수는 있지만, PEP 484에서 이는 별도의 PEP로 다루어야 한다고 결정했습니다.
  • 클래스 독스트링에 변수 타입 어노테이션 달기: 많은 프로젝트가 이미 다양한 독스트링 관례를 사용하고 있지만, 대개 일관성이 부족하고 일반적으로 아직 PEP 484 어노테이션 문법을 따르지 않습니다. 또한 이렇게 하려면 특수하고 정교한 파서가 필요합니다. 이는 결국 서드파티 타입 검사 도구와 협력한다는 이 PEP의 목적을 무너뜨릴 것입니다.
  • 디스크립터로 __annotations__ 구현하기: 이는 __annotations__에 딕셔너리도 None도 아닌 값을 설정하는 것을 막기 위해 제안되었습니다. Guido는 이 아이디어를 불필요하다며 거부했습니다. 대신 __annotations__가 매핑이 아닌 상태에서 이를 갱신하려고 하면 TypeError가 발생합니다.
  • 대입 없는 어노테이션을 global 또는 nonlocal과 동일하게 취급하기: 거부된 이 제안은 함수 본문에 대입 없는 어노테이션이 있을 때 어떠한 평가도 일어나지 않기를 원했습니다. 이와 달리 이 PEP는 대상이 단일 이름보다 복잡한 경우, 그 대상이 정의되어 있음을 강제하기 위해서라도 그 “왼쪽 부분”을 함수 본문에서 나타나는 지점에서 평가해야 한다고 봅니다. 예를 들어, 다음 예시에서:
    def foo(self):
        slef.name: str
    

    slef라는 이름이 평가되어야 합니다. 그래야 이 이름이 정의되어 있지 않은 경우(이 예시에서는 그럴 가능성이 높습니다 :-) 런타임에 오류가 잡힙니다. 이는 초깃값이 있는 경우에 일어나는 일과 더 잘 맞으므로, 예상치 못한 동작이 더 적을 것으로 기대됩니다. (또한 대상이 self.name이었다면(이번에는 철자가 올바릅니다 :-), 최적화 컴파일러는 self가 반드시 정의되어 있음을 증명할 수 있는 한 이를 평가할 의무가 없다는 점에 유의하십시오.)

하위 호환성

이 PEP는 하위 호환성을 완전히 유지합니다.

구현

Python 3.6용 구현은 GitHub에서 확인할 수 있습니다.