1주차 — 타입 힌트와 dataclass
타입 힌트 문법, typing 모듈(Optional/Union/Callable), 그리고 보일러플레이트를 줄여주는 @dataclass 와 frozen 옵션까지 — 견고한 코드의 출발점입니다.
이 강의에서 배우는 것
- 1타입 힌트 문법(int, str, list[int], Optional)을 익힌다
- 2mypy 로 정적 타입을 검사한다
- 3@dataclass 로 데이터 클래스를 간결히 작성한다
- 4field(default_factory=list) 의 의의를 안다
1. 타입 힌트 기본
런타임에는 영향이 없지만, IDE 자동완성·정적 분석·문서화에 큰 도움이 됩니다.
def greet(name: str, age: int = 20) -> str:
return f"{name}({age})"
count: int = 0
items: list[str] = []2. typing 모듈
from typing import Optional, Union, Any, Callable
def find_user(uid: int) -> Optional[dict]:
"""Optional[X] = X | None"""
...
def parse(value: Union[str, int]) -> int:
"""str 또는 int (Python 3.10+ 에서는 str | int)"""
...
handler: Callable[[int], str] = lambda x: str(x)Python 3.10+ 부터는 더 짧게 쓸 수 있습니다.
def find_user(uid: int) -> dict | None: ...
def parse(value: str | int) -> int: ...3. 컨테이너 타입
nums: list[int] = [1, 2, 3]
scores: dict[str, int] = {"Alice": 90}
pair: tuple[int, str] = (1, "a")
unique: set[int] = {1, 2, 3}4. mypy
pip install mypy
mypy main.py
mypy --strict main.py # 엄격 모드5. dataclass
__init__, __repr__, __eq__ 가 자동 생성됩니다.
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
p = Point(1, 2)
print(p) # Point(x=1, y=2)
print(p == Point(1, 2)) # True6. dataclass 옵션
from dataclasses import dataclass, field
@dataclass
class Book:
title: str
author: str
tags: list[str] = field(default_factory=list)
price: float = 0.0
b = Book("Python", "Guido")
b.tags.append("language")tags: list[str] = [] 는 모든 인스턴스가 공유합니다. 절대 안 됨 — 반드시 field(default_factory=list).
7. frozen dataclass
불변 객체. 해시 가능 → set/dict 키로 사용 가능합니다.
@dataclass(frozen=True)
class Point:
x: int
y: int
p = Point(1, 2)
# p.x = 99 # FrozenInstanceError
{Point(1, 2)} # OK자주 하는 실수
- 가변 기본값을 그대로 — field(default_factory=list) 사용
- Optional 의미 오해 — Optional[int] = int | None. '선택적 인자'가 아님
- 타입 힌트가 런타임에 검증된다고 오해 — 그냥 힌트일 뿐. 런타임 검증은 pydantic
FAQ
Q1. 타입 힌트를 다 달아야 하나요? — 공개 API(함수 시그니처)는 강력 권장. 내부 변수는 추론으로 충분.
Q2. dataclass vs pydantic.BaseModel — dataclass는 표준, pydantic은 런타임 검증·직렬화 강력. 데이터 처리 많으면 pydantic.
Q3. Any 를 쓰면? — mypy가 검사를 포기. 가능하면 더 구체적인 타입.
💻 예제 (examples)
실제로 실행해 결과를 확인할 수 있는 예제 코드입니다.
def greet(name: str, age: int = 20) -> str:
return f"{name}({age})"
count: int = 0
items: list[str] = ["사과", "바나나"]
print(greet("홍길동"))
print(greet("김파이", 28))
print(count, items)홍길동(20)
김파이(28)
0 ['사과', '바나나']from typing import Optional, Callable
def find_user(uid: int) -> Optional[dict]:
return {"id": 1, "name": "Alice"} if uid == 1 else None
def apply(f: Callable[[int], int], x: int) -> int:
return f(x)
print(find_user(1))
print(find_user(99))
print(apply(lambda n: n * n, 5)){'id': 1, 'name': 'Alice'}
None
25from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
p1 = Point(1, 2)
p2 = Point(1, 2)
print(p1)
print(p1 == p2)Point(x=1, y=2)
Truefrom dataclasses import dataclass, field
@dataclass
class Book:
title: str
tags: list[str] = field(default_factory=list)
@dataclass(frozen=True)
class Coord:
x: int
y: int
b = Book("Python")
b.tags.append("language")
print(b)
print({Coord(1, 2), Coord(1, 2), Coord(3, 4)})Book(title='Python', tags=['language'])
{Coord(x=1, y=2), Coord(x=3, y=4)}📝 과제 (exercises)
직접 풀어보고, 막힐 때 정답을 펼쳐 비교해보세요.
함수에 타입 힌트
목표: 기본 함수 시그니처에 타입 힌트를 추가한다.
- add(a: int, b: int) -> int 타입 힌트 부여
- join(words: list[str], sep: str = ', ') -> str 타입 힌트 부여
기본값이 있어도 타입 힌트는 콜론 뒤에 그대로 쓴다
5
사과, 바나나, 포도- · mypy main.py 가 오류 없이 통과
- · 함수가 정상 동작
▶정답 코드 펼치기 / 접기
def add(a: int, b: int) -> int:
return a + b
def join(words: list[str], sep: str = ", ") -> str:
return sep.join(words)
print(add(2, 3))
print(join(["사과", "바나나", "포도"]))5
사과, 바나나, 포도Book/Member dataclass
목표: 도서관 도메인을 dataclass 로 모델링한다.
- @dataclass Book(title, author, tags: list[str] = field(default_factory=list))
- @dataclass Member(name, age: int)
- 두 인스턴스를 만들어 출력
가변 기본값은 field(default_factory=list)
Book(title='파이썬', author='홍길동', tags=['언어'])
Member(name='Alice', age=25)- · dataclass 데코레이터 사용
- · field 사용 정확
- · 출력 일치
▶정답 코드 펼치기 / 접기
from dataclasses import dataclass, field
@dataclass
class Book:
title: str
author: str
tags: list[str] = field(default_factory=list)
@dataclass
class Member:
name: str
age: int
b = Book("파이썬", "홍길동")
b.tags.append("언어")
m = Member("Alice", 25)
print(b)
print(m)Book(title='파이썬', author='홍길동', tags=['언어'])
Member(name='Alice', age=25)Optional 시그니처
목표: Optional 을 사용하는 함수 5개를 작성한다.
- find_user(uid: int) -> Optional[dict]
- first(items: list[int]) -> Optional[int]
- div(a: int, b: int) -> Optional[float]
- get_age(name: str) -> Optional[int]
- parse_int(s: str) -> Optional[int]
반환할 값이 없으면 None 반환
{'id': 1}
1
None
None
42- · 5개 모두 Optional 반환 타입
- · None 반환 케이스 처리
▶정답 코드 펼치기 / 접기
from typing import Optional
DB = {1: {"id": 1}, 2: {"id": 2}}
AGES = {"Alice": 25}
def find_user(uid: int) -> Optional[dict]:
return DB.get(uid)
def first(items: list[int]) -> Optional[int]:
return items[0] if items else None
def div(a: int, b: int) -> Optional[float]:
return None if b == 0 else a / b
def get_age(name: str) -> Optional[int]:
return AGES.get(name)
def parse_int(s: str) -> Optional[int]:
try:
return int(s)
except ValueError:
return None
print(find_user(1))
print(first([1, 2, 3]))
print(div(10, 0))
print(get_age("Bob"))
print(parse_int("42")){'id': 1}
1
None
None
42