728x90
반응형
Overview
Python 코드를 작성하다 보면 `TypeVar('T')` 나 `Generic[T]` 를 사용하게 된다.
코드를 복사해서 쓰긴 하는데, 정확히 뭘 하는 건지 모르겠다면?
이번 글에서 TypeVar와 제네릭의 개념부터 실전 활용까지 완벽하게 정리한다.

TypeVar란 무엇인가?
기본 개념
from typing import TypeVar
T = TypeVar('T')
TypeVar는 "제네릭 타입 변수"를 선언하는 것이다.
쉽게 말하면
- T는 "어떤 타입이든 될 수 있는" 플레이스홀더
- 사용할 때 구체적인 타입이 결정됨
- 한 번 정해지면 그 타입으로 고정됨
제네릭이 필요한 이유
문제 상황: 타입이 불명확한 코드
# 제네릭 없이
class ProcessResult:
def __init__(self, data, success: bool):
self.data = data # 타입이 뭔지 모름
self.success = success
result = ProcessResult([1, 2, 3], True)
# IDE가 result.data의 타입을 알 수 없음
# 자동완성 안됨, 타입 체크 불가능
문제점
- data가 무슨 타입인지 알 수 없음
- IDE 자동완성 지원 안됨
- 타입 체크가 불가능해 런타임 에러 발생 가능
해결책: 제네릭 사용
# 제네릭 사용
from typing import Generic, TypeVar
T = TypeVar('T')
class ProcessResult(Generic[T]):
def __init__(self, data: T, success: bool):
self.data: T = data
self.success = success
# 사용 시 타입 지정
result: ProcessResult[list[int]] = ProcessResult([1, 2, 3], True)
# IDE가 result.data가 list[int]인 걸 인식!
# 자동완성 지원, 타입 체크 가능
장점
- 타입 안정성 확보
- IDE 자동완성 지원
- 코드 가독성 향상
- 버그 사전 방지
실전 활용 예시
1. 기본 사용법
from typing import Generic, TypeVar
T = TypeVar('T')
class Box(Generic[T]):
def __init__(self, content: T):
self.content: T = content
def get(self) -> T:
return self.content
# 사용 예시
int_box: Box[int] = Box(123)
str_box: Box[str] = Box("Hello")
list_box: Box[list[str]] = Box(["a", "b", "c"])
print(int_box.get()) # 123 (타입: int)
print(str_box.get()) # "Hello" (타입: str)
2. 여러 타입 변수 사용
from typing import Generic, TypeVar
K = TypeVar('K')
V = TypeVar('V')
class KeyValuePair(Generic[K, V]):
def __init__(self, key: K, value: V):
self.key = key
self.value = value
# 사용 예시
pair1: KeyValuePair[str, int] = KeyValuePair("age", 25)
pair2: KeyValuePair[int, list[str]] = KeyValuePair(1, ["a", "b"])
3. 제약이 있는 TypeVar
from typing import TypeVar
# 숫자 타입만 허용
T = TypeVar('T', int, float)
def add(a: T, b: T) -> T:
return a + b
result1 = add(1, 2) # ✅ OK (int)
result2 = add(1.5, 2.5) # ✅ OK (float)
# result3 = add("a", "b") # ❌ 에러 (str은 허용 안됨)
비교 분석: Any vs Generic
시나리오 1: Any 사용
from typing import Any
class Container:
def __init__(self, value: Any):
self.value = value
def get(self) -> Any:
return self.value
container = Container(123)
result = container.get()
# IDE: result의 타입을 알 수 없음 (Any)
# result.upper() # 런타임 에러!
시나리오 2: Generic 사용
from typing import Generic, TypeVar
T = TypeVar('T')
class Container(Generic[T]):
def __init__(self, value: T):
self.value = value
def get(self) -> T:
return self.value
container: Container[int] = Container(123)
result = container.get()
# IDE: result가 int임을 정확히 인식
# result.upper() # IDE가 미리 경고!
비교표
| 항목 | Any | Generic[T] |
| 타입 안정성 | 없음 | 강력함 |
| IDE 자동완성 | 불가능 | 완벽 지원 |
| 타입 체크 | 불가능 | 가능 |
| 유연성 | 매우 높음 | 제한적 |
| 권장 용도 | 레거시 코드 | 신규 코드 |
흔한 실수와 해결법
실수 1: TypeVar 선언만 하고 활용 안함
# 잘못된 사용
T = TypeVar('T')
class ProcessResult: # Generic[T] 빠짐!
def __init__(self, data: T, success: bool):
self.data = data
self.success = success
# 제네릭이 작동 안함!
올바른 사용
# 올바른 사용
T = TypeVar('T')
class ProcessResult(Generic[T]): # Generic[T] 상속!
def __init__(self, data: T, success: bool):
self.data: T = data
self.success = success
실수 2: 타입 힌트 생략
# 타입 힌트 없음
def process(data):
result = ProcessResult(data, True)
return result
# 타입 힌트 명시
def process(data: T) -> ProcessResult[T]:
result = ProcessResult(data, True)
return result
실수 3: 여러 TypeVar를 섞어서 사용
# 잘못된 사용
T = TypeVar('T')
U = TypeVar('U')
class Wrong(Generic[T]):
def method(self, value: U) -> U: # U는 클래스 레벨에 없음!
return value
# 올바른 사용
class Correct(Generic[T, U]):
def method(self, value: U) -> U:
return value
실무 활용 사례
API 응답 래퍼
from typing import Generic, TypeVar, Optional
T = TypeVar('T')
class ApiResponse(Generic[T]):
def __init__(
self,
data: Optional[T] = None,
error: Optional[str] = None,
status_code: int = 200
):
self.data = data
self.error = error
self.status_code = status_code
def is_success(self) -> bool:
return self.error is None and self.data is not None
# 사용 예시
class User:
def __init__(self, id: int, name: str):
self.id = id
self.name = name
def get_user(user_id: int) -> ApiResponse[User]:
# API 호출
user = User(user_id, "홍길동")
return ApiResponse(data=user, status_code=200)
response = get_user(123)
if response.is_success():
user = response.data # 타입: Optional[User]
print(user.name) # IDE가 User 타입임을 인식!
데이터 캐시 구현
from typing import Generic, TypeVar, Dict, Optional
from datetime import datetime, timedelta
K = TypeVar('K')
V = TypeVar('V')
class Cache(Generic[K, V]):
def __init__(self, ttl_seconds: int = 300):
self._cache: Dict[K, tuple[V, datetime]] = {}
self._ttl = timedelta(seconds=ttl_seconds)
def set(self, key: K, value: V) -> None:
self._cache[key] = (value, datetime.now())
def get(self, key: K) -> Optional[V]:
if key not in self._cache:
return None
value, timestamp = self._cache[key]
if datetime.now() - timestamp > self._ttl:
del self._cache[key]
return None
return value
# 사용 예시
user_cache: Cache[int, User] = Cache(ttl_seconds=600)
user_cache.set(123, User(123, "홍길동"))
user = user_cache.get(123) # 타입: Optional[User]
if user:
print(user.name)
TypeVar 고급 기능
1. bound 파라미터
from typing import TypeVar
# T는 반드시 Number의 서브클래스여야 함
class Number:
def __init__(self, value: float):
self.value = value
T = TypeVar('T', bound=Number)
def double(n: T) -> T:
n.value *= 2
return n
2. covariant와 contravariant
from typing import TypeVar
# covariant: 서브타입 관계 유지
T_co = TypeVar('T_co', covariant=True)
# contravariant: 서브타입 관계 역전
T_contra = TypeVar('T_contra', contravariant=True)
요약 및 체크리스트
핵심 요약
| 항목 | 설명 |
| T | "어떤 타입이든 될 수 있는" 플레이스홀더 |
| TypeVar('T') | 제네릭 타입 변수 선언 |
| Generic[T] | 클래스를 제네릭으로 만듦 |
| 목적 | 타입 안정성 + 재사용성 |
| 효과 | IDE 자동완성, 타입 체크 |
사용 전 체크리스트
TypeVar를 선언했는가?
T = TypeVar('T')
Generic[T]를 상속했는가?
class MyClass(Generic[T]):
타입 힌트를 명시했는가?
def __init__(self, data: T):
self.data: T = data
사용 시 타입을 지정했는가?
result: MyClass[int] = MyClass(123)
결론
TypeVar와 제네릭은 Python의 타입 시스템을 강력하게 만드는 핵심 도구이다.
언제 사용해야 하나?
- 여러 타입을 다루는 컨테이너 클래스
- API 응답 래퍼
- 캐시, 큐 등 자료구조
- 재사용 가능한 유틸리티 함수
사용의 이점
- 타입 안정성 확보
- IDE 지원 최대화
- 코드 가독성 향상
- 런타임 에러 사전 방지
Reference
https://docs.python.org/3/library/typing.html
https://typing.python.org/en/latest/reference/generics.html
https://mypy.readthedocs.io/en/stable/generics.html
https://medium.com/pythoneers/understanding-typevar-in-python-f78e5108471d
728x90
반응형
'Launguage' 카테고리의 다른 글
| Python 예외 처리와 컨텍스트 매니저 완벽 가이드 (0) | 2025.12.19 |
|---|---|
| Python 상속과 다형성 완벽 가이드 (0) | 2025.12.05 |
| Python 클래스 데코레이터 완벽 가이드 (0) | 2025.11.21 |
| Python 매직 메서드 완벽 가이드 (0) | 2025.11.07 |
| Python 메서드의 3가지 종류: Instance, Class, Static Method 완벽 정리 (1) | 2025.10.24 |