[Draft] PEP 718 - Subscriptable functions

원문 링크: PEP 718 - Subscriptable functions

상태: Draft 유형: Standards Track 작성일: 23-Jun-2023

PEP 718 – Subscriptable functions (서브스크립트 가능한 함수)

개요 (Abstract)

이 PEP는 타이핑(typing) 목적으로 함수 객체(function objects)를 서브스크립트 가능하게 만드는 것을 제안합니다. 이를 통해 개발자는 타입 체커(type checker)가 생성하는 타입에 대해 명시적인 제어를 할 수 있게 됩니다. 이는 익명 함수의 파라미터 타입을 추론할 수 있는 양방향 추론(bi-directional inference)이나 특수화(specialisation)와 같은 기존 방식이 불충분한 경우에 특히 유용합니다. 또한, 이 제안은 함수가 일반 클래스와 마찬가지로 서브스크립트(subscript)될 수 있도록 하여 일관성을 제공합니다.

동기 (Motivation)

현재 파이썬의 타입 시스템에는 제네릭(generic) 함수의 타입 파라미터를 추론하기 어렵거나 불가능한 몇 가지 상황이 있습니다. 이 PEP는 이러한 문제점들을 해결하고자 합니다.

알 수 없는 타입 (Unknown Types)

특정 상황에서 제네릭 함수의 타입 파라미터를 추론하는 것이 불가능합니다.

예를 들어:

def make_list[T](*args: T) -> list[T]: ...
reveal_type(make_list()) # 타입 체커가 T에 대한 의미 있는 타입을 추론할 수 없음

FunctionType 인스턴스를 서브스크립트 가능하게 만들면, 이 생성자에 타입을 명시할 수 있습니다.

reveal_type(make_list[int]()) # 타입은 list[int]

현재로서는 정확한 타입을 제공하기 위해 할당을 사용해야 합니다.

x: list[int] = make_list()
reveal_type(x) # 타입은 list[int]

그러나 이 코드는 간단한 함수 호출을 위해 여러 줄을 차지하여 불필요하게 장황합니다.

유사하게, 다음 예시에서 T는 현재 의미 있게 추론될 수 없으므로, 추가적인 할당 없이는 xuntyped 상태가 됩니다.

def factory[T](func: Callable[[T], Any]) -> Foo[T]: ...
reveal_type(factory(lambda x: "Hello World" * x))

함수 객체가 서브스크립트 가능하다면, 더 구체적인 타입을 지정할 수 있습니다.

reveal_type(factory[int](lambda x: "Hello World" * x)) # 타입은 Foo[int]

결정 불가능한 추론 (Undecidable Inference)

일부 경우에는 서브클래스(subclass) 관계 때문에 타입 추론이 불가능할 수 있습니다. 그러나 함수를 특수화(specialise)할 수 있다면, 타입 체커는 의미 있는 타입을 추론할 수 있습니다.

def foo[T](x: Sequence[T] | T) -> list[T]: ...
reveal_type(foo[bytes](b"hello"))

현재 타입 체커는 이러한 경우에 일관적으로 타입을 합성하지 못합니다.

해결 불가능한 타입 파라미터 (Unsolvable Type Parameters)

현재 특수화되지 않은 리터럴(unspecialised literals)의 경우, 다음과 같은 상황에서 타입을 결정하는 것이 불가능합니다.

def foo[T](x: list[T]) -> T: ...
reveal_type(foo([])) # 타입 체커가 T를 추론할 수 없음 (다시 한번)
``````python
reveal_type(foo[int]([])) # 타입은 int

또한, 특정 타입이 함수에 미리 전달되어야 하는 경우에 이를 지정할 수 있다는 점도 유용합니다.

words = ["hello", "world"]
foo[int](words) # Invalid: list[str]은 list[int]와 호환되지 않음

서브스크립션을 허용함으로써 함수와 메서드는 제네릭 클래스와 일관성을 갖게 됩니다. 제안된 모든 변경사항은 호출 가능한 제네릭 클래스를 사용하여 구현될 수 있지만, 문법적 설탕(syntactic sugar)은 매우 환영받을 것입니다.

이로 인해 함수를 특수화하고 새로운 팩토리(factory)로 사용하는 것이 가능합니다.

make_int_list = make_list[int]
reveal_type(make_int_list()) # 타입은 list[int]

모노모피즘 및 재구현 (Monomorphisation and Reification)

이 제안은 또한 모노모피즘(monomorphisation)과 재구현된 타입(reified types)의 가능성을 열어줍니다. 이는 여러 번 요청되었던 기능의 문을 열어줄 수 있습니다. 참고: 이 기능 자체는 이 PEP에서 제안되는 것은 아니지만, 미래에 구현될 수 있습니다.

이러한 기능의 문법은 다음과 같을 수 있습니다.

def foo[T](): return T.__value__
assert foo[int]() is int

근거 (Rationale)

이 PEP에서 “함수 객체(Function objects)”는 FunctionType, MethodType, BuiltinFunctionType, BuiltinMethodType, MethodWrapperType을 의미합니다.

MethodType의 경우 다음과 같이 작성할 수 있어야 합니다.

class Foo:
    def make_list[T](self, *args: T) -> list[T]: ...
Foo().make_list[int]()

그리고 이는 FunctionType과 유사하게 작동해야 합니다.

BuiltinFunctionType의 경우, 내장 제네릭 함수(예: maxmin)가 파이썬에서 정의된 함수처럼 작동해야 합니다. 내장 함수는 가능한 한 파이썬으로 구현된 함수처럼 동작해야 합니다.

BuiltinMethodTypeBuiltinFunctionType과 동일한 타입입니다.

MethodWrapperType (예: object().__str__의 타입)은 제네릭 매직 메서드(generic magic methods)에 유용합니다.

명세 (Specification)

함수 객체는 런타임에 서브스크립션을 허용하기 위해 __getitem__을 구현해야 합니다. 이는 __origin__이 호출 가능한 객체(callable)로 설정되고 __args__가 전달된 타입으로 설정된 types.GenericAlias 인스턴스를 반환해야 합니다.

타입 체커는 함수 서브스크립션을 지원해야 하며, 함수 서브스크립션에 전달되는 파라미터가 제네릭 호출 가능 클래스(generic callable class)와 동일한 규칙을 따라야 한다는 것을 이해해야 합니다.

__orig_class__ 설정

현재 __orig_class__GenericAlias.__call__에서 호출된 클래스를 생성한 GenericAlias의 인스턴스로 설정되는 속성입니다. 예를 들어:

class Foo[T]: ...
assert Foo[int]().__orig_class__ == Foo[int]

현재 __orig_class__는 무조건적으로(unconditionally) 설정됩니다. 그러나 생성된 인스턴스에서 잠재적인 타입 소거(type erasure)를 방지하기 위해, __origin__이 어떤 함수 객체의 인스턴스인 경우에는 이 속성을 설정해서는 안 됩니다.

다음 코드 스니펫은 이 변경 없이는 런타임에 실패할 것입니다. 왜냐하면 __orig_class__Foo[int]가 아니라 bar[str]이 될 것이기 때문입니다.

def bar[U](): return Foo[int]()
assert bar[str]().__orig_class__ == Foo[int]

@typing.overload와의 상호작용

오버로드(overload)된 함수는 런타임 타입에 영향을 미치지 않으므로 기존과 거의 동일하게 작동해야 합니다. 유일한 변경 사항은 더 많은 상황에서 타입이 결정 가능해지고, 오버로드의 순서나 유니온(unions)에 맡기는 대신 개발자가 동작/오버로드를 명시적으로 지정할 수 있게 된다는 것입니다.

하위 호환성 (Backwards Compatibility)

현재 이러한 클래스들은 서브클래싱(subclassing)이 불가능하므로, 이미 __getitem__을 구현하고 있는 클래스와 관련하여 하위 호환성 문제는 없습니다.

참조 구현 (Reference Implementation)

제안된 런타임 변경 사항은 다음에서 확인할 수 있습니다: https://github.com/Gobot1234/cpython/tree/function-subscript

감사의 글 (Acknowledgements)

이 PEP에 대한 피드백을 제공해준 Alex Waygood와 Jelle Zijlstra, 그리고 동기를 부여하는 예시를 제공해준 Guido에게 감사드립니다.

이 문서는 퍼블릭 도메인(public domain)에 공개되거나 CC0-1.0-Universal 라이선스(둘 중 더 자유로운 것)가 적용됩니다.

⚠️ 알림: 이 문서는 AI를 활용하여 번역되었으며, 기술적 정확성을 보장하지 않습니다. 정확한 내용은 반드시 원문을 확인하시기 바랍니다.

Comments