들어가며
Python에서 TypeVar를 사용할 때마다 헷갈린다면 이 글이 도움이 될 것이다. 문법도 간단하고 원리도 어렵지 않은데, 처음에는 낯설 수 있다. 이 글에서는 TypeVar가 무엇인지, 어떻게 사용하는지, 타입 체커가 어떻게 검사하는지까지 전부 다루어볼 것이다.
목차
- TypeVar란 무엇인가?
- 실제로 어떻게 사용하는가?
- 왜 필요한가?
- 문법 정리
- 타입 체커는 어떻게 검사하는가?
- 인자와 리턴값 모두에서 꼭 사용해야 하는가?
- TypeVar 사용의 핵심
- 정리
TypeVar란 무엇인가?
TypeVar는 타입 변수이다. 일반 변수가 값을 저장하듯이, TypeVar는 타입을 변수처럼 다루게 해준다.
from typing import TypeVar
T = TypeVar('T') # T는 어떤 타입이라도 될 수 있는 타입 변수
변수와의 비유
일반 변수를 살펴보자:
x = 5 # x는 5라는 값을 가짐
x = "hello" # x는 "hello"라는 값으로 변경 가능
TypeVar도 비슷하다:
T = TypeVar('T') # T는 어떤 타입이라도 될 수 있음
차이점은 T는 타입을 담는다는 것이다.
실제로 어떻게 사용하는가?
제네릭(generic) 함수를 만들 때 사용한다. 제네릭 함수는 여러 타입을 유연하게 받을 수 있으면서도 타입 안전성을 유지하는 함수이다.
가장 간단한 예시
from typing import TypeVar
T = TypeVar('T')
def get_first_item(items: list[T]) -> T:
"""리스트의 첫 번째 항목을 반환"""
return items[0]
이 함수의 의미는 다음과 같다:
- 함수에 들어오는 리스트가
int이면, 반환도int - 함수에 들어오는 리스트가
str이면, 반환도str - 함수에 들어오는 리스트가
dict이면, 반환도dict
입력 타입과 출력 타입이 같다는 것을 명확히 표현하는 것이다.
왜 필요한가?
TypeVar 없이 작성한다면:
def get_first_item(items: list) -> any:
return items[0]
이렇게 하면 타입 힌트가 거의 무의미하다. IDE의 자동완성이나 타입 체커가 제대로 도움을 주지 못한다.
TypeVar를 사용하면:
def get_first_item(items: list[T]) -> T:
return items[0]
타입이 명확해지므로 IDE가 반환 타입을 정확히 알 수 있고, 코드를 작성할 때 훨씬 도움이 된다.
문법 정리
T = TypeVar('T')
T= 변수 이름 (관례상 대문자 영어 하나 사용)'T'= 문자열로 된 이름 (내부적으로 사용됨)
둘이 같은 이름이어야 한다. Python의 관례일 뿐이지만 반드시 지켜야 한다.
타입 체커는 어떻게 검사하는가?
타입 체커(예: mypy)는 코드를 실행하지 않고 타입이 맞는지 검사하는 도구이다.
pip install mypy
mypy your_file.py
검사 방식
from typing import TypeVar
T = TypeVar('T')
def get_first_item(items: list[T]) -> T:
return items[0]
# ✅ OK - 정수 리스트 입력, 정수 반환
result_int = get_first_item([1, 2, 3])
print(result_int + 10) # 괜찮음
# ✅ OK - 문자열 리스트 입력, 문자열 반환
result_str = get_first_item(["a", "b", "c"])
print(result_str.upper()) # 괜찮음
# ❌ ERROR - 타입 불일치 감지!
result = get_first_item([1, 2, 3])
print(result.upper()) # mypy가 에러 보고함
# "int"에는 upper() 메서드가 없다고 경고
타입 체커의 논리
- 함수 호출 분석:
get_first_item([1, 2, 3])를 분석함 - T 추론: 리스트가
list[int]이므로, T는int라고 결정 - 반환 타입 확인: T가 int이므로 반환값도
int - 사용처 검증:
result.upper()를 확인했는데, int에 upper() 없음 → ❌ 에러 보고
조금 더 복잡한 예시
from typing import TypeVar
T = TypeVar('T')
def swap(a: T, b: T) -> tuple[T, T]:
"""두 값을 바꾸기"""
return (b, a)
# ✅ OK - 두 매개변수 타입이 같음
result = swap(1, 2) # T = int
# ❌ ERROR - 두 매개변수 타입이 다름!
result = swap(1, "hello") # T가 뭐지? int? str? → 에러!
타입 체커는 swap(1, "hello")를 보고 혼란스러워한다:
- 첫 번째 인자는
int→ T는 int여야 함 - 두 번째 인자는
str→ T는 str여야 함 - 모순! → ❌ 에러 보고
인자와 리턴값 모두에서 꼭 사용해야 하는가?
아니다. 상황에 따라 다르다.
인자만 사용
T = TypeVar('T')
def process(item: T) -> None:
"""입력받은 값을 처리하기만 하고 반환 없음"""
print(item)
이는 완전히 괜찮다. T는 어떤 타입이든 받을 수 있지만 반환값이 없으므로 T를 리턴에 사용하지 않았다.
리턴값만 사용
T = TypeVar('T')
def create_default(default_value: T) -> T:
"""기본값을 받아서 그대로 반환"""
return default_value
이것도 문제없다.
같은 T를 여러 곳에 사용
T = TypeVar('T')
def first_and_last(items: list[T]) -> T:
"""리스트의 마지막 항목을 반환"""
return items[-1]
T는 리스트의 원소 타입과 반환값의 타입이 같다는 것을 표현한다.
TypeVar 사용의 핵심
TypeVar를 사용하는 목적은:
"이 여러 곳의 타입들이 서로 연관되어 있다"는 것을 표현하는 것
따라서:
- 같은 타입 관계가 없으면 사용하지 않는 것이 낫고
- 다른 타입 관계가 있으면 다른 타입 변수를 만들어서 구분한다
나쁜 예시
T = TypeVar('T')
def example(a: T) -> int: # ❌ 굳이 T를 사용할 이유가 없음
return len(str(a))
a가 어떤 타입이든 상관없고 무조건 int를 반환하므로 T를 사용할 이유가 없다.
def example(a: object) -> int: # ✅ 이렇게 명확하게
return len(str(a))
이것이 훨씬 낫고 명확하다.
정리
TypeVar는 타입 변수이다. 일반 변수가 값을 담듯이 타입을 담는다.- 제네릭 함수를 만들 때 사용하여 여러 타입을 유연하게 받으면서도 타입 안전성을 유지한다.
- 타입 간의 관계를 표현하는 것이 핵심이다. 무조건 둘 다 사용해야 하는 것이 아니라, 의미 있는 관계가 있을 때만 사용한다.
- 타입 체커는 TypeVar로 표현된 타입의 일관성을 검증하여 런타임 에러를 미리 방지해준다.
TypeVar를 제대로 이해하고 사용하면, 코드의 타입 안전성이 크게 향상되고 유지보수도 훨씬 쉬워진다. 처음에는 낯설 수 있지만 몇 번 사용하면 금방 익숙해질 것이다.
'Python > 문법' 카테고리의 다른 글
| [Python] TypeVar와 Generic (0) | 2025.11.23 |
|---|---|
| [Python] 비동기(async) 프로그래밍 5 (0) | 2025.05.23 |
| [Python] 비동기(async) 프로그래밍 4 (0) | 2025.05.22 |
| [Python] 비동기(async) 프로그래밍 3 (0) | 2025.05.22 |
| [Python] 비동기(async) 프로그래밍 2 (0) | 2025.05.22 |
