더 편한 타입 힌트를 위한 PEP695

python 3.12에 새롭게 추가/변경되는 문법

created at: 2023-07-22


PEP란

PEP는 파이썬 커뮤니티에서 논의된 주제를 문서화 한 것이다. 대표적으로 PEP20PEP8이 있다.

이번에 얘기하고자 하는 것은 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

간단한 타입 제한

타입의 범위를 좁히는 것 또한 다음과 같이 간결하게 변경되었다.

old
from typing import Generic, TypeVar

ValueT_co = TypeVar("ValueT_co", covariant=True, bound=str)


class ClassA(Generic[ValueT_co]):
    def method1(self) -> ValueT_co:
        ...
new
class ClassA[T: str]:
    def method1(self) -> T:
        ...

간단한 유형 별칭

또한 TypeAlias에 대한 새로운 구문이 추가되었다.

old
from typing import TypeAlias, TypeVar

ValueT = TypeVar("ValueT")

ListOrSet: TypeAlias = list[ValueT] | set[ValueT]
new
type ListOrSet[T] = list[T] | set[T]

여담

python 3.12에 새롭게 적용될 PEP다 보니, 실제로 이 문법을 적용하여 개발할 일은 상당히 먼 일이긴 하다.

그래도 내부적으로 사용할 몇몇 모듈정도는...?