Python 타입 힌트: 더 안전하고 명확한 코드 작성
여러분은 아마 입력에 따라 다른 타입을 반환하는 Python 함수나 중간에 타입이 바뀌는 변수를 본 적이 있을 것입니다. 이러한 모호함은 런타임 오류를 일으키지만, 미리 잡을 수 있었던 문제입니다. Python 타입 힌트는 예상 타입을 명시할 수 있게 하여 코드를 자기 문서화하고, 정적 분석 도구가 실행 전에 실수를 잡아내도록 도와줍니다.
이 글에서는 기본 어노테이션부터 고급 패턴까지 Python에서 타입 힌트를 효과적으로 사용하는 방법과, 이것이 어떻게 더 안전하고 명확한 코드에 기여하는지 살펴봅니다.
Python 타입 힌트란?
타입 힌트(타입 어노테이션이라고도 함)는 Python 3.5(PEP 484)에서 도입된 문법으로, 변수, 함수 매개변수, 반환 값의 예상 타입을 지정할 수 있습니다. 런타임에 강제되지는 않지만 mypy, pyright, IDE와 같은 정적 타입 검사기가 타입 관련 오류를 감지하는 데 사용됩니다.
예를 들어:
def greet(name: str) -> str:
return f"Hello, {name}"
여기서 name: str은 name이 문자열이어야 함을 나타내고, -> str은 함수가 문자열을 반환함을 나타냅니다.
타입 힌트를 사용하는 이유
- 조기 버그 발견: 정적 타입 검사기가 코드를 실행하기 전에 타입 불일치를 잡아낼 수 있습니다.
- 가독성 향상: 타입 힌트는 인라인 문서 역할을 하여 다른 사람(그리고 미래의 자신)이 코드를 더 쉽게 이해할 수 있게 합니다.
- 더 나은 IDE 지원: 자동 완성, 리팩토링, 탐색이 더 정확해집니다.
- 유지보수성 향상: 리팩토링할 때 타입 힌트가 계약을 깨지 않도록 도와줍니다.
- 협업 촉진: 팀이 예상 인터페이스를 명확하게 소통할 수 있습니다.
기본 타입 어노테이션
변수, 함수 매개변수, 반환 타입을 어노테이션할 수 있습니다.
변수
age: int = 30
name: str = "Alice"
함수
def add(a: int, b: int) -> int:
return a + b
컬렉션
리스트, 딕셔너리 등에는 typing 모듈(Python 3.9+에서는 내장 제네릭)을 사용하세요:
from typing import List, Dict
def process(items: List[str]) -> Dict[str, int]:
return {item: len(item) for item in items}
Python 3.9+에서는 list[str]과 dict[str, int]를 직접 사용할 수 있습니다.
고급 타입 힌트
코드가 커지면 더 복잡한 시나리오를 만나게 됩니다. 다음은 몇 가지 고급 기능입니다.
Optional과 Union
Optional[T]는 Union[T, None]의 축약형으로, T 타입이거나 None일 수 있는 값을 나타냅니다.
from typing import Optional
def find_user(user_id: int) -> Optional[str]:
# returns username or None if not found
...
Python 3.10+에서는 | 연산자를 사용할 수 있습니다: str | None.
Callable
함수를 인자로 받을 때:
from typing import Callable
def apply_func(func: Callable[[int], int], value: int) -> int:
return func(value)
제네릭
타입 변수를 사용하여 재사용 가능한 컴포넌트를 만드세요:
from typing import TypeVar, List
T = TypeVar('T')
def first(items: List[T]) -> T:
return items[0]
TypedDict
고정된 키 집합과 값 타입을 가진 딕셔너리용:
from typing import TypedDict
class User(TypedDict):
name: str
age: int
def greet(user: User) -> str:
return f"Hello, {user['name']}"
정적 타입 검사기 사용하기
타입 힌트는 검사할 때만 유용합니다. 인기 있는 도구로는 mypy, pyright, pyre가 있습니다. mypy 사용법을 알아보겠습니다.
- mypy 설치:
pip install mypy - 코드에 실행:
mypy your_script.py - 보고된 오류 수정.
예를 들어, 다음이 주어졌을 때:
def add(a: int, b: int) -> int:
return a + b
add("1", "2")
mypy는 다음과 같이 보고합니다: error: Argument 1 to "add" has incompatible type "str"; expected "int".
mypy.ini 또는 pyproject.toml 파일을 통해 mypy를 구성하여 더 엄격한 검사를 적용할 수 있습니다.
타입 힌트 모범 사례
- 일관성 유지: 모든 공개 함수와 메서드에 어노테이션을 추가하세요.
Any는 신중하게 사용: 해당 값에 대한 타입 검사를 비활성화합니다.Any나object보다 구체적인 타입을 선호하세요.- None이 될 수 있는 값에는
Optional을 사용하세요. - 구조적 서브타이핑(타입 안전성을 갖춘 덕 타이핑)에는
Protocol을 활용하세요. - 리팩토링할 때 타입 힌트를 최신 상태로 유지하세요.
- CI에서 타입 검사기를 실행하여 문제를 조기에 잡으세요.
흔한 함정과 피하는 방법
- 타입 오류 무시: mypy를 침묵시키지 말고 문제를 이해하고 수정하세요.
Any남용: 타입 힌트의 목적을 무색하게 만듭니다.- 반환 타입 어노테이션 누락: 특히 None을 반환하는 함수의 경우.
- 타입 힌트와 함께 가변 기본 인자 사용: 이는 별개의 Python 함정이지만 타입 힌트가 잡아내지 못합니다.
- 변경 후 힌트를 업데이트하지 않음: 잘못된 확신을 초래합니다.
실전 타입 힌트: 작은 예제
숫자 리스트를 처리하고 평균을 반환하는 함수를 생각해 보세요. 타입 힌트가 없으면 어떤 타입이 예상되는지 불분명합니다.
def average(numbers):
return sum(numbers) / len(numbers)
타입 힌트를 사용하면:
from typing import List
def average(numbers: List[float]) -> float:
return sum(numbers) / len(numbers)
이제 누군가 문자열 리스트를 전달하면 mypy가 이를 지적합니다.
워크플로우에 타입 힌트 통합하기
타입 힌트를 최대한 활용하려면 개발 프로세스에 통합하세요:
- 기존 코드베이스에 점진적으로 타입 힌트를 추가하세요.
- IDE가 타입 오류를 표시하도록 구성하세요.
- CI 파이프라인에 타입 검사 단계를 추가하세요.
- pre-commit 훅을 사용하여 커밋 전에 mypy를 실행하세요.
예를 들어, 간단한 GitHub Actions 워크플로우 단계:
- name: Type check
run: mypy .
FAQ
타입 힌트가 런타임 성능에 영향을 미치나요?
아니요, 타입 힌트는 런타임에 무시됩니다. __annotations__에 저장되지만 실행 속도에는 영향을 주지 않습니다.
이전 Python 버전에서 타입 힌트를 사용할 수 있나요?
타입 힌트는 Python 3.5에서 도입되었습니다. 이전 버전에서는 주석 기반 어노테이션(예: # type: int)을 사용할 수 있지만, 지원되는 Python 버전으로 업그레이드하는 것이 좋습니다.
타입 힌트에서 List와 list의 차이는 무엇인가요?
typing의 List는 Python 3.5-3.8에서 사용됩니다. Python 3.9+에서는 내장 list를 직접 사용할 수 있습니다. 둘 다 타입 검사에 동등합니다.
코드 품질을 개선할 준비가 되셨나요? 오늘 한 함수에 타입 힌트를 추가하고 mypy를 실행하여 이점을 확인해보세요. 더 많은 개발자 도구를 원하시면 JSON Formatter를 확인하여 JSON 데이터를 손쉽게 예쁘게 만들고 검증하세요.