C.W.K.
Stream
Lesson 04 of 05 · published

Generic — TypeVar / Generic / Self

~22 min · generic, typevar, self, paramspec

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

왜 generic — 타입을 parametric 하게

가끔 함수나 클래스가 어떤 타입에든 작동, 근데 "입력과 출력이 같은 타입" 표현 원함. def first(items: list[T]) -> T 가 "T 의 list 가 T 반환". T 가 타입 변수 — 타입 체커가 호출별 해결하는 placeholder.

TypeVar — 일꾼

T = TypeVar("T") 가 타입 변수 생성. 함수 시그니처 또는 클래스 파라미터로 사용 (베이스에 Generic[T] 와). Bound 변수 — T = TypeVar("T", bound=Comparable) — T 를 Comparable 의 서브타입으로 제한. Constrained — T = TypeVar("T", int, str) — T 를 특정 set 의 하나로 제한.

Generic 클래스 — Stack[int] vs Stack[str]

원소 타입에 generic 한 클래스. 시그니처에 T 사용하는 메서드 가진 class Stack(Generic[T]). Stack[int]() 가 int stack 생성, Stack[str]() 가 string stack 생성. 타입 체커가 원소 타입 추론 + 연산 검증.

Self — "나와 같은 클래스"

자기 클래스 반환하는 메서드가 종종 "Self 반환" 말하고 싶음. 3.11 전엔 TypeVar("T", bound="MyClass") 우회. Python 3.11 이 정확히 이 케이스용 typing.Self 추가 — def chain(self) -> Self: 가 "실제로 호출한 클래스 무엇이든 반환". 유연 인터페이스와 서브클래스의 factory 메서드에 유용.

ParamSpec — 전체 호출 시그니처 보존

함수 감싸는 데코레이터엔 "wrapper 가 wrapped 함수와 같은 인자 받음" 말하고 싶음. P = ParamSpec("P") 가 전체 파라미터 list (positional + keyword) 캡쳐. 데코레이터 힌트 — def wrap(fn: Callable[P, R]) -> Callable[P, R]. FastAPI 같은 도구가 많이 사용.

Code

TypeVar — generic 함수·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 작동
Generic 클래스 — Stack[T]·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 (3.11+) — 유연 인터페이스용 반환 타입·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 X
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

Generic 클래스 Cache(Generic[K, V]) 구현 — get(key: K) -> V | None, set(key: K, value: V) -> None. 두 TypeVar 사용. 그 다음 Cache[str, int]() 만들고 잘못된 타입 넘기면 타입 체커가 잡는 거 보여. 그 다음 ParamSpec 사용한 데코레이터 log_calls — wrapped 함수의 전체 시그니처 보존. (머릿속) mypy 가 원래 시그니처 유지 확인.

Progress

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

댓글 0

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

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