Python 타입 힌트는 *어노테이션*. 소스 코드에 앉아있고, 함수의 __annotations__ 에 저장, 정적 타입 체커 (mypy, pyright) 와 IDE 가 읽음. Python 자체가 런타임에 강제 X. def f(x: int) -> str: 가 런타임에 list 받음 — 계약은 도구 레벨, 인터프리터 X.
기본 문법
파라미터에 : type, 반환값에 -> type. 변수도 : type. 필요한 기본 타입 — int, float, str, bool, None, bytes. 컨테이너엔 현대 Python (3.9+) 가 소문자 — list[int], dict[str, int], tuple[int, str], set[str]. typing 의 대문자 List, Dict 는 옛 스타일, 여전히 작동, 근데 이젠 소문자 선호.
Optional / Union / | 연산자
Optional[X] 가 X | None — "X 또는 None". Union[X, Y] 가 X | Y (3.10+). 파이프 문법이 현대 idiom, import 된 이름 여전히 가능 but 더 안 읽힘. def f(x: int | None = None) -> str | None: 가 "없을 수도" 의 표준 모양.
Any — 타입 체커 escape hatch
Any 가 "체크 안 함" 의미. 타입 체커가 Any 값에 어떤 연산도 받음. 타입 없는 라이브러리 인터페이스 또는 진짜 동적 데이터에 유용. 어디나 뿌리지 마 — 타이핑 의미 없게 함. "어떤 객체" 의미면 object (체크됨), Any 는 opt-out 에 예약.
원칙: 타입 힌트 = 체크되는 문서. 함수가 뭘 기대하고 뭘 반환하는지 독자에게, 편집 시점에 실수 잡는 형태로 알려줘. 비용 작음, 버그 방지 가치는 코드베이스 크기에 비례. 현대 Python 스타일 — 모든 거 타입 힌트, 타이핑이 이제 디폴트 기대.
Code
기본 어노테이션·python
def greet(name: str, age: int) -> str:
return f"hi {name}, age {age}"
# 변수 어노테이션
user_id: int = 42
users: list[str] = ["alice", "bob"]
config: dict[str, int] = {"port": 8000, "workers": 4}
# 어노테이션이 함수에 저장
print(greet.__annotations__)
# {'name': <class 'str'>, 'age': <class 'int'>, 'return': <class 'str'>}
# 런타임은 강제 X — 런타임에 작동, mypy 가 잡음
greet(123, "oops")
현대 컨테이너 타입 — 소문자·python
# 옛 스타일 (여전히 작동)
from typing import List, Dict, Tuple, Set
def old_style(items: List[int]) -> Dict[str, int]:
return {str(i): i for i in items}
# 현대 스타일 (3.9+) — 선호
def modern(items: list[int]) -> dict[str, int]:
return {str(i): i for i in items}
# Tuple — 고정 크기와 가변 크기
Point = tuple[float, float] # 정확히 2 floats
MixedTuple = tuple[int, str, bool] # 위치별 특정 타입
IntList = tuple[int, ...] # 가변 길이, 모두 int
Optional 와 파이프 — None 처리·python
# 옛 스타일
from typing import Optional, Union
def old(x: Optional[int]) -> Union[str, None]:
if x is None:
return None
return str(x)
# 현대 (3.10+)
def modern(x: int | None) -> str | None:
if x is None:
return None
return str(x)
# 디폴트 값 패턴 — None 의 가장 흔한 사용
def fetch(key: str, default: str | None = None) -> str | None:
return {"a": "alpha"}.get(key, default)
Any vs object — 어느 거 언제·python
from typing import Any
def accept_anything(x: Any) -> int:
# 타입 체커가 x 에 *어떤* 연산도 허용
return x.completely_made_up_method() # mypy: OK
def accept_object(x: object) -> int:
# 타입 체커가 속성 접근 전 narrow 요구
if isinstance(x, str):
return len(x)
raise TypeError
# Any 사용 — 타입 없는 라이브러리, 진짜 동적 데이터, gradual typing.
# object 사용 — '어떤 객체', 나중에 isinstance 로 narrow 할 때.
Alias — 복잡한 타입 명명·python
# 타입 alias 가 복잡한 타입에 한 번 이름 붙이게
from typing import Callable
UserId = int
UserData = dict[str, str | int | None]
Callback = Callable[[str, int], bool]
def process_user(uid: UserId, data: UserData, on_done: Callback) -> None:
if on_done("start", uid):
print("processed", data)
# 3.12+ 명시적 type 문
# type UserId = int — TypeAlias 로 인식
# 더 자세한 건 lesson 5.
함수 top_n(items: list[dict[str, int]], n: int = 3, key: str = 'score') -> list[dict[str, int]] 작성 — key 내림차순으로 top-n 항목 반환. 곳곳에 적절한 타입 힌트. dict 셋 list ('score' 키 가진) 넘겨 테스트. 그 다음 일부러 list 대신 int 로 호출 — 작동하지만 mypy 가 표시하는 거 관찰.
Progress
Progress is local-only — sign in to sync across devices.