본문 바로가기
C.W.K.
Stream
Lesson 04 of 05 · published

제네릭 — 입력과 출력 사이의 타입 관계

~22 min · generic, typevar, self, paramspec

Level 0호기심
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete

TypeVar는 호출마다 정해지는 타입 자리라서 list[T] -> T처럼 관계를 보존해. bound는 어떤 상위 계약의 하위 타입으로, constraints는 열거한 타입 중 하나로 제한해. 클래스는 Generic[T]로 같은 관계를 필드와 메서드에 이어.

Self는 실제 호출한 클래스와 같은 타입을 돌려준다는 뜻이고, ParamSpec은 데코레이터가 원래 함수의 모든 인자 모양을 보존하게 해. TypeVar가 한 번만 나오면 관계를 설명하지 못하므로 구체 타입이나 Protocol이 나아.

Code

TypeVar로 입력과 출력 연결하기·python
from typing import TypeVar

T = TypeVar("T")

def first(items: list[T]) -> T:
    return items[0]

x: int = first([1, 2, 3])             # T = int
y: str = first(["a", "b", "c"])       # T = str

# Bound TypeVar — T 가 float 의 서브타입이어야
from typing import TypeVar
N = TypeVar("N", bound=float)

def average(values: list[N]) -> float:
    return sum(values) / len(values)

print(average([1, 2, 3]))             # int 작동 — int 가 float-ish 의 서브타입
print(average([1.5, 2.5]))            # float 작동
원소 타입을 보존하는 제네릭 Stack·python
from typing import Generic, TypeVar

T = TypeVar("T")

class Stack(Generic[T]):
    def __init__(self):
        self._items: list[T] = []

    def push(self, item: T) -> None:
        self._items.append(item)

    def pop(self) -> T:
        return self._items.pop()

    def peek(self) -> T:
        return self._items[-1]

int_stack: Stack[int] = Stack()
int_stack.push(1)
int_stack.push(2)
print(int_stack.pop())                # 2 — int 로 타입 체크

str_stack: Stack[str] = Stack()
str_stack.push("hello")
# str_stack.push(42)                  # mypy 에러 — str 기대
같은 동적 클래스를 돌려주는 Self·python
from typing import Self

class QueryBuilder:
    def __init__(self):
        self.filters: list[str] = []

    def where(self, condition: str) -> Self:
        self.filters.append(condition)
        return self

    def order_by(self, field: str) -> Self:
        self.filters.append(f"ORDER BY {field}")
        return self

# 서브클래스 — 메서드가 SubQuery를 반환하며 QueryBuilder가 아님
class SubQuery(QueryBuilder):
    def with_extra(self) -> Self:
        self.filters.append("EXTRA")
        return self

sq = SubQuery()
result: SubQuery = sq.where("x = 1").order_by("id").with_extra()
# Self 가 체인 내내 반환 타입이 SubQuery 보장
함수 시그니처를 지키는 ParamSpec·python
from typing import Callable, ParamSpec, TypeVar
import functools
import time

P = ParamSpec("P")
R = TypeVar("R")

def timed(fn: Callable[P, R]) -> Callable[P, R]:
    @functools.wraps(fn)
    def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
        start = time.perf_counter()
        result = fn(*args, **kwargs)
        print(f"{fn.__name__}: {(time.perf_counter()-start)*1000:.2f}ms")
        return result
    return wrapper

@timed
def add(a: int, b: int) -> int:
    return a + b

# 타입 체커가 알아 — add 가 데코레이터 후에도
# 시그니처 (int, int) -> int. ParamSpec 이 보존.

External links

Exercise

K와 V를 쓰는 Cache를 만들어 get과 set의 키·값 관계를 보존해. 이어서 ParamSpec을 쓴 log_calls 데코레이터로 감싼 함수의 원래 시그니처가 타입 검사기에 남는지 확인해.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인 — 댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.