PEP 563 – 어노테이션 평가 지연
- Author:
- Łukasz Langa <lukasz at python.org>
- Discussions-To:
- Python-Dev list
- Status:
- Superseded
- Type:
- Standards Track
- Topic:
- Typing
- Created:
- 08-Sep-2017
- Python-Version:
- 3.7
- Post-History:
- 01-Nov-2017, 21-Nov-2017
- Superseded-By:
- 649, 749
- Resolution:
- Python-Dev message
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
결론
이 PEP에서 제안된 기능들은 결코 기본 동작이 되지 않았으며, PEP 649와 PEP 749에서 제안된 어노테이션의 지연 평가로 대체되었습니다.
초록
PEP 3107은 함수 어노테이션을 위한 문법을 도입했지만, 의미는 의도적으로 정의되지 않은 채로 남겨졌습니다. PEP 484는 어노테이션에 표준적인 의미, 즉 타입 힌트를 도입했습니다. PEP 526은 변수 어노테이션을 정의하여 타입 힌트 사용 사례와 명시적으로 연결했습니다.
이 PEP는 함수 어노테이션과 변수 어노테이션이 더 이상 함수 정의 시점에 평가되지 않도록 변경할 것을 제안합니다. 대신, 이들은 문자열 형태로 __annotations__에 보존됩니다.
이 변경 사항은 Python 3.7에서 __future__ 임포트로 시작하여 점진적으로 도입되고 있습니다.
근거와 목표
PEP 3107은 함수 정의의 일부에 임의의 어노테이션을 지원하는 기능을 추가했습니다. 기본값과 마찬가지로, 어노테이션은 함수 정의 시점에 평가됩니다. 이는 타입 힌트 사용 사례에 여러 문제를 일으킵니다:
- 순방향 참조: 타입 힌트에 아직 정의되지 않은 이름이 포함된 경우, 그 정의는 문자열 리터럴로 표현되어야 합니다;
- 타입 힌트는 모듈 임포트 시점에 실행되는데, 이는 계산 비용이 들지 않는 것이 아닙니다.
어노테이션의 평가를 지연시키면 두 문제가 모두 해결됩니다. 참고: PEP 649는 위의 문제들에 대한 대안적인 해결책을 제안하며, 이로 인해 이 PEP는 대체될 위험에 처해 있습니다.
목표가 아닌 것
관련 PEP 484와 PEP 526에서와 마찬가지로, Python은 계속 동적 타입 언어로 남을 것이며, 저자들은 관례상으로라도 타입 힌트를 의무화할 생각이 전혀 없다는 점을 강조해야 합니다.
이 PEP는 타입 어노테이션에서의 전방 참조 문제를 해결하기 위한 것입니다. 어노테이션 외부에도 전방 참조가 문자열 리터럴 사용을 요구하는 경우가 여전히 존재합니다. 그러한 경우들은 이 문서의 뒷부분에 나열되어 있습니다.
강제 평가가 없는 어노테이션은 타입 힌트의 구문을 개선할 기회를 제공합니다. 이 아이디어는 별도의 PEP를 필요로 하며, 이 문서에서는 더 이상 논의하지 않습니다.
타이핑 이외의 어노테이션 사용
어노테이션은 여전히 타입 검사 외의 임의 용도로도 사용할 수 있지만, 이 PEP의 설계와 그 전신들(PEP 484와 PEP 526)이 주로 타입 힌트 사용 사례에 의해 동기가 부여되었다는 점은 언급할 가치가 있습니다.
파이썬 3.8에서 PEP 484는 잠정 상태를 벗어나게 됩니다. 파이썬 프로그래밍 언어에 대한 PEP 544, PEP 557, 또는 PEP 560과 같은 다른 개선 사항들은, PEP 484에서 정의한 타입 어노테이션과 typing 모듈에 의존하기 때문에, 이미 이 기반 위에서 구축되고 있습니다. 사실 PEP 484가 파이썬 3.7에서 잠정 상태로 남아 있는 이유는, 앞서 언급한 개선 사항들 중 일부가 요구하는 빠른 진화를 한 릴리스 주기 더 허용하기 위함입니다.
이를 염두에 두면, 앞서 언급한 PEP들과 호환되지 않는 어노테이션 사용은 폐기 예정으로 간주해야 합니다.
구현
이 PEP를 통해 함수와 변수 어노테이션은 더 이상 정의 시점에 평가되지 않습니다. 대신 문자열 형태가 해당 __annotations__ 딕셔너리에 보존됩니다. 정적 타입 검사기는 동작에 차이를 느끼지 못하지만, 런타임에 어노테이션을 사용하는 도구는 지연 평가를 수행해야 합니다.
문자열 형태는 컴파일 단계에서 AST로부터 얻어지며, 이는 문자열 형태가 원본 소스의 정확한 형식을 보존하지 못할 수도 있음을 의미합니다. 참고: 어노테이션이 이미 문자열 리터럴이었더라도, 여전히 문자열로 감싸집니다.
어노테이션은 리터럴 문자열로 전달될 때도(즉 compile(literal, '', 'eval')) 문법적으로 유효한 파이썬 표현식이어야 합니다. 어노테이션은 모듈 스코프에 존재하는 이름만 사용할 수 있는데, 이는 지역 이름을 사용한 지연 평가가 신뢰할 수 없기 때문입니다(typing.get_type_hints()로 해석되는 클래스 수준 이름만이 유일한 예외입니다).
관련 PEP 526에 따라, 지역 변수 어노테이션은 함수의 클로저 외부에서 접근할 수 없으므로 전혀 평가되지 않는다는 점에 유의하십시오.
파이썬 3.7에서 향후 동작 활성화하기
위에서 설명한 기능은 파이썬 3.7부터 다음과 같은 특수한 import를 사용하여 활성화할 수 있습니다.:
from __future__ import annotations
이 기능의 참조 구현은 GitHub에서 확인할 수 있습니다.
런타임에 타입 힌트 해석하기
런타임에 어노테이션을 문자열 형태에서 내부 표현식의 결과로 해석하려면, 사용자 코드가 그 문자열을 평가해야 합니다.
타입 힌트를 사용하는 코드의 경우, typing.get_type_hints(obj, globalns=None, localns=None) 함수가 표현식을 문자열 형태로부터 올바르게 다시 평가합니다. 타입 어노테이션이 문자열 리터럴로 표현될 수 있으므로, 현재 __annotations__를 사용하는 모든 유효한 코드는 이미 그렇게 하고 있어야 한다는 점에 유의하십시오.
다른 목적으로 어노테이션을 사용하는 코드의 경우, 일반적인 eval(ann, globals, locals) 호출만으로 어노테이션을 해석하기에 충분합니다.
두 경우 모두 전역 및 지역 네임스페이스가 지연 평가에 미치는 영향을 고려하는 것이 중요합니다. 어노테이션은 더 이상 정의 시점에 평가되지 않으며, 더 중요하게는 정의된 동일한 스코프에서 평가되지 않습니다. 따라서 일반적으로 어노테이션에서 지역 상태를 사용하는 것은 더 이상 불가능합니다. 전역 네임스페이스의 경우에는 어노테이션이 정의된 모듈이 지연 평가를 위한 올바른 컨텍스트입니다.
get_type_hints() 함수는 함수와 클래스에 대한 올바른 globalns의 값을 자동으로 확인합니다. 또한 클래스에 올바른 localns를 자동으로 제공합니다.
eval()을 실행할 때 전역 네임스페이스의 값은 다음과 같은 방법으로 수집할 수 있습니다.
- 함수 객체는
__globals__라는 속성에 해당 전역 네임스페이스에 대한 참조를 보유합니다. - 클래스는 자신이 정의된 모듈의 이름을 보유하며, 이를 사용하여 해당 전역 네임스페이스를 가져올 수 있습니다.:
cls_globals = vars(sys.modules[SomeClass.__module__])
모든
__annotations__를 평가하려면 기본 클래스에 대해서도 이 작업을 반복해야 한다는 점에 유의하십시오. - 모듈은 자체
__dict__를 사용해야 합니다.
호출 시점의 스택 프레임이 더 이상 존재하지 않을 가능성이 매우 높기 때문에 함수의 localns값은 안정적으로 가져올 수 없습니다.
클래스의 경우, 해당 클래스와 기본 클래스의 vars를 메서드 해석 순서에 따라 연결하여 localns를 구성할 수 있습니다. 슬롯은 클래스가 정의된 후에만 채울 수 있으므로, 이 목적을 위해 슬롯을 참조할 필요는 없습니다.
런타임 어노테이션 해석 및 클래스 데코레이터
현재 클래스의 어노테이션을 해석해야 하는 메타클래스와 클래스 데코레이터는 현재 클래스의 이름을 사용하는 어노테이션에서 실패합니다. 예시:
def class_decorator(cls):
annotations = get_type_hints(cls) # raises NameError on 'C'
print(f'Annotations for {cls}: {annotations}')
return cls
@class_decorator
class C:
singleton: 'C' = None
이는 이 PEP 이전에도 이미 그러했습니다. 클래스 데코레이터는 현재 정의 스코프에서 클래스에 이름이 할당되기 전에 클래스에 적용됩니다.
런타임 어노테이션 해석 및 TYPE_CHECKING
때로는 타입 검사기가 확인해야 하지만 실행되어서는 안 되는 코드가 있습니다. 이러한 경우를 위해 typing 모듈은 상수를 정의하며, TYPE_CHECKING은 타입 검사 중에는 True로 간주되지만 런타임에는 False로 간주됩니다. 예시:
import typing
if typing.TYPE_CHECKING:
import expensive_mod
def a_func(arg: expensive_mod.SomeClass) -> None:
a_var: expensive_mod.SomeClass = arg
...
이 접근 방식은 임포트 순환을 처리할 때도 유용합니다.
런타임에 typing.get_type_hints()를 사용하여 a_func의 어노테이션을 해석하려고 하면 expensive_mod이 정의되어 있지 않으므로 실패합니다(TYPE_CHECKING 변수가 런타임에 False이기 때문입니다). 이는 이 PEP 이전에도 이미 그러했습니다.
하위 호환성
이는 하위 호환성이 없는 변경입니다. 임의의 객체가 어노테이션에 직접 존재하는 것에 의존하는 애플리케이션은 typing.get_type_hints()나 eval()을 사용하지 않는 경우 중단됩니다.
함수 정의 시점의 지역 변수에 의존하는 어노테이션은 나중에 해석할 수 없습니다. 예시:
def generate():
A = Optional[int]
class C:
field: A = 1
def method(self, arg: A) -> None: ...
return C
X = generate()
나중에 X의 어노테이션을 get_type_hints(X)를 사용하여 확인하려고 하면 A와 이를 둘러싼 스코프가 더 이상 존재하지 않으므로 실패합니다. 이러한 어노테이션은 정적으로 성공적으로 분석할 수 있는 경우가 많고 이것이 어노테이션의 주된 사용 사례이므로, Python은 이러한 어노테이션을 금지하려고 시도하지 않습니다.
중첩 클래스를 사용하고 각 클래스의 상태를 참조하는 어노테이션도 여전히 유효합니다. 로컬 이름이나 완전 수식 이름을 사용할 수 있습니다. 예시:
class C:
field = 'c_field'
def method(self) -> C.field: # this is OK
...
def method(self) -> field: # this is OK
...
def method(self) -> C.D: # this is OK
...
def method(self) -> D: # this is OK
...
class D:
field2 = 'd_field'
def method(self) -> C.D.field2: # this is OK
...
def method(self) -> D.field2: # this FAILS, class D is local to C
... # and is therefore only available
# as C.D. This was already true
# before the PEP.
def method(self) -> field2: # this is OK
...
def method(self) -> field: # this FAILS, field is local to C and
# is therefore not visible to D unless
# accessed as C.field. This was already
# true before the PEP.
구문적으로 유효한 표현식이 아닌 어노테이션이 있으면 컴파일 시점에 SyntaxError가 발생합니다. 그러나 해당 시점에는 이름이 확인되지 않으므로, 사용된 이름이 올바른지 여부를 검증하려고 시도하지 않습니다.
사용 중단 정책
Python 3.7부터는 설명된 기능을 사용하려면 __future__ import가 필요합니다. 경고는 발생하지 않습니다.
참고: PEP 649에 대한 결정이 내려지기를 기다리는 현재로서는 이것이 결국 기본 동작이 될지는 분명하지 않습니다. 어떤 경우든 즉시 평가에 의존하는 어노테이션의 사용은 두 제안 모두와 호환되지 않으며 더 이상 지원되지 않습니다.
순방향 참조
모듈에서 이름이 정의되기 전에 의도적으로 사용하는 것을 순방향 참조라고 합니다. 이 절에서는 if TYPE_CHECKING: 블록 내에서 가져오거나 정의된 모든 이름도 순방향 참조라고 부르겠습니다.
이 PEP는 타입 어노테이션의 순방향 참조 문제를 다룹니다. 이 경우에는 문자열 리터럴을 더 이상 사용할 필요가 없습니다. 그러나 typing 모듈에는 언어의 다른 구문 구성 요소를 사용하는 API가 있으며, 이러한 API에서는 여전히 문자열 리터럴을 사용하여 순방향 참조를 우회해야 합니다. 목록에는 다음이 포함됩니다.
- 타입 정의:
T = TypeVar('T', bound='<type>') UserId = NewType('UserId', '<type>') Employee = NamedTuple('Employee', [('name', '<type>'), ('id', '<type>')])
- 별칭:
Alias = Optional['<type>'] AnotherAlias = Union['<type>', '<type>'] YetAnotherAlias = '<type>'
- 캐스팅:
cast('<type>', value)
- 베이스 클래스:
class C(Tuple['<type>', '<type>']): ...
구체적인 경우에 따라 위에 나열된 일부 경우에는 if TYPE_CHECKING: 블록에 사용 코드를 배치하여 우회할 수 있습니다. 이는 런타임에 사용할 수 있어야 하는 코드에는 작동하지 않으며, 특히 베이스 클래스와 캐스팅에는 적용되지 않습니다. 명명된 튜플의 경우 Python 3.6에서 도입된 새로운 클래스 정의 구문을 사용하면 문제가 해결됩니다.
일반적으로 모든 순방향 참조의 문제를 해결하려면 현재의 단일 패스 하향식 모델에서 Python의 모듈 인스턴스화 수행 방식을 변경해야 합니다. 이는 언어의 주요 변경 사항이 되며 이 PEP의 범위를 벗어납니다.
거부된 아이디어
어노테이션을 정의할 때 함수 지역 상태를 사용할 수 있는 기능 유지
평가를 지연하면 어노테이션이 생성된 프레임에 대한 참조를 유지해야 합니다. 예를 들어 모든 어노테이션을 문자열 대신 람다로 저장하면 이를 구현할 수 있습니다.
프레임이 모든 객체를 계속 살아 있게 유지하므로, 어노테이션이 많이 붙은 코드에서는 비용이 감당할 수 없을 정도로 커집니다. 여기에는 주로 다시는 접근되지 않을 객체가 포함됩니다.
클래스 수준 범위를 다룰 수 있으려면 람다 방식은 인터프리터에 새로운 종류의 셀을 필요로 합니다. 이렇게 하면 __annotations__에 나타날 수 있는 타입의 수가 크게 늘어나며, 문자열만큼 introspection하기도 어렵습니다.
중첩 클래스의 경우 정의 시점에 유효한 “globals”와 “locals”를 가져오는 기능이 typing.get_type_hints()에서 제공된다는 점에 유의하십시오.
함수가 로컬 변수를 사용해야 하는 어노테이션이 있는 클래스나 함수를 생성하는 경우, 컴파일러에 의존하지 않고 주어진 생성 객체의 __annotations__ 딕셔너리를 직접 채울 수 있습니다.
클래스에서도 로컬 상태 사용 금지
이 PEP는 원래 클래스에서도 어노테이션 내 이름을 모델 수준 범위의 이름만 허용하도록 제한할 것을 제안했습니다. 저자는 이렇게 하면 로컬 이름과 모듈 수준 이름이 충돌하는 경우를 포함하여 이름 해석이 모호하지 않게 된다고 주장했습니다.
이 아이디어는 결국 클래스의 경우에는 거부되었습니다. 대신 클래스 수준 어노테이션이 필요한 경우 로컬 네임스페이스를 올바르게 채우도록 typing.get_type_hints()가 수정되었습니다.
이 아이디어를 거부한 이유는 이것이 Python에서 스코프가 작동하는 방식에 대한 직관에 어긋나며, 기존 타입 어노테이션을 충분히 많이 깨뜨려 전환을 번거롭게 만들기 때문입니다. 마지막으로, 클래스 데코레이터가 타입 어노테이션을 평가할 수 있으려면 로컬 범위에 접근해야 합니다. 이는 클래스 데코레이터가 클래스가 바깥쪽 범위에서 이름을 받기 전에 적용되기 때문입니다.
대신 문자열 리터럴 형식을 위한 새로운 딕셔너리 도입
Yury Selivanov는 다음과 같은 아이디어를 공유했습니다.
- 함수에 새로운 특수 속성인
__annotations_text__를 추가하십시오. __annotations__를 지연 방식의 동적 매핑으로 만들어,__annotations_text__의 해당 키에 있는 식을 적시에 평가하도록 하십시오.
이 아이디어는 새로운 __future__ 임포트의 필요성을 없애 하위 호환성 문제를 해결하기 위한 것입니다. 안타깝게도 이것만으로는 충분하지 않습니다. 지연 평가는 어노테이션이 어떤 상태에 접근할 수 있는지를 변경합니다. 지연 평가는 전방 참조 문제를 해결하지만, 더 이상 함수 수준의 로컬 변수에 접근할 수 없게 만들기도 합니다. 이것만으로도 하위 호환성이 깨지는 원인이 되며, 사용 중단 기간을 둘 충분한 근거가 됩니다.
__future__ 임포트는 새로운 기능을 사용하기로 선택했음을 나타내는 명백하고 명시적인 표시입니다. 또한 외부 도구가 이전 방식과 새로운 방식을 사용하는 Python 파일의 차이를 쉽게 식별할 수 있게 합니다. 전자의 경우 해당 도구는 로컬 상태에 접근할 수 있음을 식별하고, 후자의 경우에는 전방 참조가 허용됨을 식별합니다.
마지막으로, 이후 get_type_hints()를 사용한다면 __annotations__에서 적시에 평가하는 것은 불필요한 단계입니다.
-O를 사용하여 어노테이션 제거
이 PEP의 목적에는 이것이 만족스럽지 않은 이유가 두 가지 있습니다.
첫째, 이는 런타임 비용만 다룰 뿐 전방 참조는 다루지 않으므로, 전방 참조를 소스 코드에서 여전히 안전하게 사용할 수 없습니다. 라이브러리 유지 관리자는 전방 참조를 사용할 수 없는데, 그렇게 하면 라이브러리 사용자가 새로운 가상의 -O 스위치를 사용하도록 강제하기 때문입니다.
둘째로, 이는 아기까지 목욕물과 함께 버리는 셈입니다. 이제 아무런 런타임 어노테이션 사용도 수행할 수 없습니다. PEP 557은 런타임에 타입 어노테이션을 평가하는 것이 유용한 최근 개발의 한 예입니다.
그렇다고 하더라도, 어노테이션을 제거하는 세분화된 -O 옵션은 기존 -O 동작(독스트링과 assert 문 제거)과 개념적으로 호환되므로 향후 가능성이 있습니다. 이 PEP는 그 아이디어를 무효화하지 않습니다.
어노테이션의 문자열 리터럴을 __annotations__에 있는 그대로 전달하기
이 PEP는 원래 문자열 리터럴의 내용을 해당 키 아래 __annotations__에 직접 저장하도록 제안했습니다. 이는 런타임 타입 검사기에 대한 지원을 단순화하기 위한 것이었습니다.
Mark Shannon은 문자열이 타입 어노테이션의 일부에 불과한 상황을 처리하지 못하므로 이 아이디어에 결함이 있다고 지적했습니다.
그 불일치는 항상 명백했지만, 어쨌든 문자열을 이중으로 감싸는 경우를 완전히 방지하지도 못하므로 이를 고려할 가치는 없습니다.
future import 이름을 더 장황하게 만들기
다음 import를 요구하는 대신:
from __future__ import annotations
PEP는 기능을 더 명시적으로 부를 수 있습니다. 예를 들어 string_annotations, stringify_annotations, annotation_strings, annotations_as_strings, lazy_annotations, static_annotations 등이 있습니다.
이러한 이름의 문제는 매우 장황하다는 것입니다. lazy_annotations를 제외한 각각의 이름은 Python에서 가장 긴 future 기능 이름이 됩니다. 이러한 이름은 한 단어 형식보다 입력하기도 길고 기억하기도 어렵습니다.
지나치게 일반적으로 들리는 future import 이름이지만 실제로는 사용자에게 그 기능이 무엇을 하는지 명확했던 선례가 있습니다.:
from __future__ import division
이전 논의
PEP 484에서
전방 참조 문제는 PEP 484가 처음 초안으로 작성될 때 논의되었으며, 그 결과 문서에 다음과 같은 문장이 실리게 되었습니다:
다음과 같이__future__import를 사용하여 주어진 모듈의 모든 어노테이션을 문자열 리터럴로 변환할 수 있도록 하는 절충안이 가능합니다.:from __future__ import annotations class ImSet: def add(self, a: ImSet) -> List[ImSet]: ... assert ImSet.add.__annotations__ == { 'a': 'ImSet', 'return': 'List[ImSet]' }이러한
__future__import 문은 별도의 PEP에서 제안할 수 있습니다.
python/typing#400
이 문제는 typing 모듈의 GitHub 프로젝트에서 Issue 400에서 장시간 논의되었습니다. 해당 문제 설명에는 typing에서 가져와야 하는 제네릭 타입에 대한 비판이 포함되어 있습니다. 이는 초보자에게 혼란을 일으키는 경향이 있습니다.
왜 이것은:from typing import List, Set def dir(o: object = ...) -> List[str]: ... def add_friends(friends: Set[Friend]) -> None: ...하지만 이것은 아닌가:
def dir(o: object = ...) -> list[str]: ... def add_friends(friends: set[Friend]) -> None ...왜 이것은:
up_to_ten = list(range(10)) friends = set()하지만 이것은 아닌가:
from typing import List, Set up_to_ten = List[int](range(10)) friends = Set[Friend]()
typing 사용성은 흥미로운 문제이지만 이 PEP의 범위를 벗어납니다. 구체적으로, PEP 484에서 표준화된 typing 구문의 확장은 각각 별도의 PEP와 승인을 필요로 합니다.
Issue 400은 이 PEP에서 명시한 것처럼 어노테이션의 평가를 연기하고 __annotations__에 문자열로 유지할 것을 궁극적으로 제안합니다. 이 아이디어는 좋은 반응을 얻었습니다. Ivan Levkivskyi는 __future__ 임포트를 사용하는 방안을 지지했으며, compile.c에서 AST를 문자열로 복원할 것을 제안했습니다. Jukka Lehtosalo는 타입이 어노테이션 외부에서 사용되는 전방 참조의 일부 사례에서는 평가 연기가 도움이 되지 않는다고 지적했습니다. 이러한 경우에는 문자열 리터럴 표기법을 여전히 사용해야 합니다. 이러한 사례는 이 PEP의 “전방 참조” 절에서 간략하게 논의합니다.
이 사안에서 가장 큰 논란은 어노테이션 표현식을 다시 문자열 형식으로 토큰화 해제하는 것이 Python 프로그래밍 언어에 전례가 없으며 임시방편처럼 느껴진다는 Guido van Rossum의 우려였습니다. 그는 다음과 같이 말했습니다.
한 가지 떠오르는 점은 이것이 언어에 매우 무작위적인 변경이라는 것입니다. 표현식의 지연 실행을 나타내는 더 간결한 방법(lambda:보다 구문을 적게 사용하는 방법)이 있으면 유용할 수 있습니다. 그러나 이미 이 특정 사용 사례를 위한, 매우 최소한의 구문만 필요한 해결책이 있는데도, 더 일반적인 해결책을 제안하는 대신 타입 어노테이션의 사용 사례를 먼저 해결하기 위해 언어를 변경하는 일이 왜 그렇게 중요해야 합니까?
결국 Ethan Smith와 schollii는 PyCon US에서 수집된 의견에 따르면 전방 참조의 현재 상태를 개선해야 한다고 밝혔습니다. Guido van Rossum은 __future__ 아이디어로 돌아가자고 제안하면서, 오용을 방지하려면 어노테이션을 구문적으로 유효하게 유지하고 런타임에 올바르게 평가되도록 하는 것이 중요하다고 지적했습니다.
python-ideas에서의 첫 번째 초안 논의
논의는 주로 두 스레드, 최초 발표와 후속 논의인 PEP 563 및 값비싼 하위 호환성에서 진행되었습니다.
이 PEP는 상당히 긍정적인 반응을 얻었습니다(강력한 찬성 4명, 우려가 있는 찬성 2명, 반대 2명). 첫 번째 스레드에서 가장 크게 우려를 표명한 사람은 Steven D’Aprano였으며, 그는 PEP의 문제 정의가 하위 호환성을 깨뜨릴 정당한 이유가 되지 않는다고 검토 의견에서 밝혔습니다. 이 답변에서 Steven은 Python이 더 이상 지역 함수 또는 클래스 상태에 의존하는 어노테이션의 평가를 지원하지 않게 되는 점을 주로 우려하는 것으로 보였습니다.
몇몇 사람은 어노테이션을 타입 지정 이외의 목적으로 사용하는 라이브러리가 있다고 우려를 표명했습니다. 그러나 언급된 라이브러리 중 이 PEP로 인해 유효하지 않게 되는 라이브러리는 하나도 없습니다. 다만 올바른 globals 및 locals를 설정한 상태로 어노테이션에 eval()을 호출해야 한다는 새로운 요구 사항에 맞게 조정해야 합니다.
globals 및 locals가 올바른 상태여야 한다는 이 세부 사항은 여러 논평자가 주목했습니다. Alyssa (Nick) Coghlan은 문자열 대신 어노테이션을 람다로 변환하는 작업을 벤치마크했지만, 안타깝게도 런타임에서 현재 방식보다 훨씬 느린 것으로 나타났습니다.
후자의 스레드는 Jim J.가 시작했습니다. Jewett는 어노테이션을 올바르게 평가할 수 있는 능력이 중요한 요구 사항이며, 그 측면에서 하위 호환성이 중요하다고 강조했습니다. 그는 얼마간 논의한 후 어노테이션의 부작용은 코드 냄새이며, 평가를 수행하거나 수행하지 않도록 선택하는 모달 지원은 복잡한 해결책이라고 인정했습니다. 그의 가장 큰 우려는 전역 및 지역 범위에 대한 평가 제한에서 비롯되는 기능 손실로 여전히 남았습니다.
Alyssa Coghlan은 평가 도우미를 영리하게 구현하면 PEP에 따른 일부 평가 제한을 해제할 수 있으며, 이를 통해 클래스 데코레이터 형태에서도 자기 참조 클래스를 해결할 수 있다고 지적했습니다. 그녀는 PEP가 표준 라이브러리에 이 도우미 함수를 제공해야 한다고 제안했습니다.
python-dev에서의 두 번째 초안 논의
논의는 주로 발표 스레드에서 진행되었으며, 이어서 Mark Shannon의 게시물 아래에서 간단한 논의가 이루어졌습니다.
Steven D’Aprano는 PEP가 제안한 변경 이후 어노테이션에 오타가 허용되는 것이 받아들일 만한지 우려를 표했습니다. Brett Cannon은 타입 검사기와 (린터나 프로그래밍 텍스트 편집기 같은) 다른 정적 분석기가 이런 종류의 오류를 잡아낼 것이라고 답했습니다. Jukka Lehtosalo는 이 상황이 함수 본문의 이름이 함수가 호출될 때까지 해석되지 않는 것과 유사하다고 덧붙였습니다.
주요 논의 주제 중 하나는 어노테이션을 “썽크 형태”로, 다시 말해 클래스 수준 스코프에 접근할 수 있는 (그리고 호출 시점에 스코프 커스터마이징을 허용하는) 특수화된 람다로 저장하자는 Alyssa Coghlan의 제안이었습니다. 그는 이를 위한 가능한 설계(간접 애트리뷰트 셀)를 제시했습니다. 이는 이후 Lisp의 “특수 형식(special forms)”과 동등한 것으로 여겨졌습니다. Guido van Rossum은 이런 종류의 기능이 12주 안에 (즉 Python 3.7 베타 프리즈 전까지) 안전하게 구현될 수 없다는 우려를 표했습니다.
얼마 지나지 않아, 문자열 형태 지지자와 썽크 형태 지지자 사이의 갈림점은 실제로는 어노테이션이 일반적인 구문 요소로 인식되어야 하는지, 아니면 타입 검사 사용 사례에 결부된 무언가로 인식되어야 하는지에 관한 것임이 분명해졌습니다.
마지막으로 Guido van Rossum은 인터프리터에 새로운 구성 요소가 필요하다는 사실에 근거하여 썽크 아이디어를 거부한다고 선언했습니다. 이 구성 요소는 어노테이션에 노출되어, __annotations__에 저장될 수 있는 값의 종류(임의의 객체, 문자열, 그리고 이제 썽크까지)를 늘리게 됩니다. 게다가 썽크는 문자열만큼 인트로스펙션이 용이하지 않습니다. 가장 중요하게는, Guido van Rossum이 어노테이션의 사용을 (선택적인 런타임 구성 요소를 갖춘) 정적 타이핑으로 점진적으로 제한하는 것에 관심이 있음을 명시적으로 밝혔습니다.
Alyssa Coghlan도 PEP 563에 설득되어, __future__ 임포트의 이름을 두고 곧바로 필수적인 사소한 논쟁(bikeshedding)을 시작했습니다. 많은 토론자들은 annotations가 기능 이름으로 쓰기에는 지나치게 광범위한 이름으로 보인다는 데 동의했습니다. Guido van Rossum은 잠시 이를 string_annotations라고 부르기로 했지만, division이 명확한 의미를 가진 광범위한 이름의 선례라고 주장하며 생각을 바꿨습니다.
Mark Shannon이 토론에서 제안한 PEP에 대한 마지막 개선 사항은 문자열 리터럴을 있는 그대로 __annotations__에 전달하고 싶은 유혹을 거부한 것이었습니다.
정적 타이핑의 런타임 부담을 둘러싼 부수적인 논의 스레드도 시작되었는데, typing 모듈의 임포트 시간(의존성을 제외하면 re와 비슷하고, 의존성을 포함하면 re보다 세 배 무겁습니다)과 같은 주제가 다루어졌습니다.
감사의 말
이 문서는 Guido van Rossum, Jukka Lehtosalo, Ivan Levkivskyi의 소중한 의견, 격려, 조언 없이는 완성될 수 없었을 것입니다.
구현은 Serhiy Storchaka에 의해 철저히 검토되었으며, 그는 버그, 나쁜 가독성, 성능 문제를 비롯한 온갖 종류의 문제를 찾아냈습니다.
Copyright
This document has been placed in the public domain.