PEP란
PEP는 파이썬 커뮤니티에서 논의된 주제를 문서화 한 것이다. 대표적으로 PEP20과 PEP8이 있다.
이번에 얘기하고자 하는 것은 PEP695다.
PEP695는 기존의 타입힌트 작성 방식인 PEP484에서 다른 정적 언어와 유사한 형태로 변경하는 것으로, python 3.12에 적용 될 것으로 예정돼있다.
PEP484란
PEP484는 타입힌트에 대해 가장 먼저 논의 된 것으로, 이 PEP를 기반으로 하여 여러 보완 PEP가 작성되었다.
많은 부분에서 발전이 있었지만, 여전히 아쉬운 부분은 바로 제네릭에 표현 방식이다.
PEP484의 제네릭 표현식은 다음과 같다.
from typing import Sequence, TypeVar
ValueT = TypeVar("ValueT")
def first(seq: Sequence[ValueT]) -> ValueT:
return seq[0]
위와 같은 방식으로 변수를 정의하기에, Covariance와 Contravariance라는 다소 생소한 개념에 대해 알 필요가 있으며, 이를 고려하고 사용해야한다.
이를 고려한 제네릭 표현식은 다음과 같다.
from typing import Generic, Iterable, Iterator, TypeVar
ValueT_co = TypeVar("ValueT_co", covariant=True)
class ImmutableList(Generic[ValueT_co]):
def __init__(self, items: Iterable[ValueT_co]) -> None:
...
def __iter__(self) -> Iterator[ValueT_co]:
...
class Employee:
...
class Manager(Employee):
...
def dump_employees(emps: ImmutableList[Employee]) -> None:
...
mgrs = ImmutableList([Manager()]) # type: ImmutableList[Manager]
dump_employees(mgrs) # OK
만약 여기서 covariant
옵션을 제거하면 다음과 같이 에러를 발생시킨다.
from __future__ import annotations
from typing import Generic, Iterable, Iterator, TypeVar
ValueT = TypeVar("ValueT")
class ImmutableList(Generic[ValueT]):
def __init__(self, items: Iterable[ValueT]) -> None:
...
def __iter__(self) -> Iterator[ValueT]:
...
class Employee:
...
class Manager(Employee):
...
def dump_employees(emps: ImmutableList[Employee]) -> None:
...
mgrs = ImmutableList([Manager()]) # type: ImmutableList[Manager]
dump_employees(mgrs) # Error!
# Argument of type "ImmutableList[Manager]" cannot be assigned to parameter
# "emps" of type "ImmutableList[Employee]" in function "dump_employees"
# "ImmutableList[Manager]" is incompatible with "ImmutableList[Employee]"
# TypeVar "ValueT@ImmutableList" is invariant
# "Manager" is incompatible with
# "Employee"PylancereportGeneralTypeIssues
PEP695란
PEP695는 PEP484에서 다소 아쉬웠던 제네릭 지원을, 다른 정적 언어와 유사한 구문으로 변경하는 것이다.
PEP695에서 달라진 것
자동화된 분산 추론
앞서 에러를 발생시켰던 예시는 다음과 같이 변경하면 에러를 발생시키지 않게 된다.
from typing import Iterable, Iterator
class ImmutableList[T]:
def __init__(self, items: Iterable[T]) -> None:
...
def __iter__(self) -> Iterator[T]:
...
class Employee:
...
class Manager(Employee):
...
def dump_employees(emps: ImmutableList[Employee]) -> None:
...
mgrs = ImmutableList([Manager()]) # type: ImmutableList[Manager]
dump_employees(mgrs) # Ok
간단한 타입 제한
타입의 범위를 좁히는 것 또한 다음과 같이 간결하게 변경되었다.
from typing import Generic, TypeVar
ValueT_co = TypeVar("ValueT_co", covariant=True, bound=str)
class ClassA(Generic[ValueT_co]):
def method1(self) -> ValueT_co:
...
class ClassA[T: str]:
def method1(self) -> T:
...
간단한 유형 별칭
또한 TypeAlias
에 대한 새로운 구문이 추가되었다.
from typing import TypeAlias, TypeVar
ValueT = TypeVar("ValueT")
ListOrSet: TypeAlias = list[ValueT] | set[ValueT]
type ListOrSet[T] = list[T] | set[T]
여담
python 3.12에 새롭게 적용될 PEP다 보니, 실제로 이 문법을 적용하여 개발할 일은 상당히 먼 일이긴 하다.
그래도 내부적으로 사용할 몇몇 모듈정도는...?