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, ClassVartyping.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의 끝에 나열된 rejected ideas의 요약을 읽으십시오.

초록

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에서는 타입 검사기가 타입 검사 규칙을 변경하도록 요구하지 않습니다. 단지 타입 주석을 대체할 수 있는 더 읽기 쉬운 구문을 제공합니다.

명세

할당문 또는 어노테이션 대상의 원하는 타입을 제3자 타입 검사기에 나타내는 단일 표현식에 타입 어노테이션을 추가할 수 있습니다.:

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 매개변수에는 중첩 수준과 관계없이 어떤 타입 변수도 포함될 수 없습니다. 따라서 ClassVar[T]ClassVar[List[Set[T]]]는 모두 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

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

단일 대입 대상과 단일 오른쪽 값만 허용됩니다. 또한 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
    

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

  • 새로운 공변 타입 ClassVar[T_co]typing 모듈에 추가됩니다. 이 타입은 유효한 타입이어야 하는 단일 인자만 허용하며, 클래스 인스턴스에 설정해서는 안 되는 클래스 변수에 어노테이션을 지정하는 데 사용됩니다. 이 제한은 정적 검사기에 의해 보장되지만 런타임에는 보장되지 않습니다. 사용에 대한 예제와 설명은 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 등)와 해당 타입 검사기를 사용하는 프로젝트에서 광범위하게 사용됩니다. 그러나 주석 구문에는 근거 절에 나열된 여러 단점이 있습니다. 이 PEP는 타입 어노테이션의 필요성에 관한 것이 아니라, 그러한 어노테이션에 어떤 구문을 사용해야 하는지에 관한 것입니다.
  • 새 키워드 도입: 좋은 키워드를 선택하기는 어렵습니다. 예를 들어 var는 너무 흔한 변수 이름이므로 사용할 수 없고, 클래스 변수나 전역 변수에 사용하려면 local도 사용할 수 없습니다. 둘째, 무엇을 선택하든 __future__ 임포트가 여전히 필요합니다.
  • 키워드로 def 사용: 제안은 다음과 같습니다.:
    def primes: List[int] = []
    def captain: str
    

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

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

    xy가 모두 T 타입입니까? 아니면 T 타입이 두 항목으로 이루어진 튜플 타입이며 그 항목들이 xy에 분배된다고 보아야 합니까? 또는 x의 타입은 Any이고 y의 타입은 T라고 보아야 합니까? (이것이 함수 시그니처에서 발생할 경우의 의미는 마지막 경우입니다.) (사람인) 독자가 추측하게 두기보다는 적어도 현재로서는 이를 금지합니다.

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

    yz의 타입이 무엇이어야 하는지 모호합니다. 또한 두 번째 줄은 구문 분석하기 어렵습니다.

  • 어노테이션을 허용하는 with for 문: 이는 for에서는 실제 이터러블을 식별하기 어렵게 만들고, with에서는 CPython의 LL(1) 파서를 혼란스럽게 만들기 때문에 거부되었습니다.
  • 로컬 어노테이션을 함수 정의 시점에 평가: 이는 Guido에 의해 거부되었는데, 어노테이션의 위치가 그것이 주변 코드와 같은 스코프에 있음을 강하게 시사하기 때문입니다.
  • 변수 어노테이션도 함수 스코프에 저장: 어노테이션을 로컬에서 사용할 수 있게 하는 가치는 함수 호출마다 딕셔너리를 생성하고 채우는 비용을 상쇄할 만큼 충분하지 않습니다.
  • 대입 없이 어노테이션된 변수를 초기화: python-ideas에서 x: int에서 xNone으로 초기화하거나, 자바스크립트의 undefined와 같은 추가적인 특수 상수로 초기화하자는 제안이 있었습니다. 그러나 언어에 또 다른 싱글턴 값을 추가하면 코드 전체에서 그것을 확인해야 할 것입니다. 따라서 Guido는 이에 대해 그저 단호하게 “안 됨”이라고 말했습니다.
  • 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에서 찾을 수 있습니다.