- typing
- Python 코드에 타입 힌트를 작성하기 위해 사용하는 모듈
- 타입 힌트
- 실행 중 타입을 강제로 제한하지는 않고 IDE, 타입 검사기, 린터 등이 활용함
- 코드 가독성, IDE 자동완성, 정적 타입 검사, FastAPI/Pydantic의 데이터 검증 및 자동화 문서에 활용
Union
from typing import Union
def print_id(value: Union[int, str]):
print(value)
- 여러 타입 중 하나를 허용할 때 사용
Union[int, str]
int 또는 str 허용
- 타입 2개 이상 지정할 수 있음
- Python 3.10부터
value: int | str 로도 가능
Literal
from typing import Literal
def set_mode(mode: Literal["read", "write"]) -> None:
...
set_mode("read")
set_mode("write")
set_mode("delete")
- 허용할 값을 구체적으로 제한할 때 사용
- 런타임 강제가 아님
Union
Literal
enum vs Literal
from enum import Enum
class Mode(str, Enum):
read = "read"
write = "write"
def set_mode(mode: Mode) -> None:
...
set_mode(Mode.read)
set_mode(Mode.write)
Literal은 간단한 값 제한
- 값 목록이 여러 곳에서 재사용된다면
Enum이 더 적절
Optional
from typing import Optional
def print_name(name: Optional[str] = None):
print(name)
- 값이 없을 수도 있을 때 사용
Optional[str]은 str 혹은 None을 허용한다는 뜻
- Python 3.10부터
name: str | None 로도 가능
Optional이 인자를 생략 가능하게 하는건 아님
- 인자를 생략 가능하게 하는건 기본값
= None
Any
from typing import Any
data: Any
data = "hello"
data = [1,2,3]
print(data)
- 아무 타입이나 허용
- 타입 체크가 느슨해지므로 남발하면 좋지 않음
Callable
from typing import Callable
def add(a: int, b: int) -> float:
return float(a + b)
def run_method(func: Callable[[int, int], float]):
print(func(1,2))
run_method(add)
3.0
- 함수를 타입으로 표현
Callable[[인자 타입들], 반환 타입]
TypedDict
from typing import TypedDict
class User(TypedDict):
name: str
age: int
user: User = {
"name": "Kim",
"age": 20
}
TypedDict는 dict의 구조를 타입 힌트로 표현할 때 사용
- 주로 JSON 형태 데이터 구조나 외부 API 응답 구조를 표현할 때 유용
- 런타임 데이터 검증용이라기보다는 정적 타입 검사 목적에 가까움
- FastAPI 요청/응답에서는 보통 Pydantic의
BaseModel을 더 많이 사용
TypeVar
기본
from typing import TypeVar
T = TypeVar("T")
def get_first(items: list[T]) -> T:
return items[0]
TypeVar는 타입을 변수처럼 사용할 수 있게 해줌
T = TypeVar("T")
T는 아직 구체적으로 정해지지 않은 타입 변수
"T"는 타입 변수의 이름
- 보통 대입하는 변수명
T 와 문자열 "T"를 같게 맞춰서 씀
get_first(items: list[T]) -> T
items는 T 타입 값을 담은 리스트
- 반환값도 같은
T 타입
list[int]를 넣으면 반환 타입은 int
list[str]를 넣으면 반환 타입은 str
제약 조건걸기
T = TypeVar("T", int, str)
T = TypeVar("T", int, str)
T를 int 혹은 str 중 하나로 제한하는 제약 조건
Python 3.12+
def get_first[T](items: list[T]) -> T:
return items[0]
- Python 3.12 이상부터는
T = TypeVar("T")를 따로 만들지 않아도 됨
Generic
기본
from typing import TypeVar, Generic
T = TypeVar("T")
class Box(Generic[T]):
def __init__(self, value: T):
self.value = value
def get_value(self) -> T:
return self.value
TypeVar
- 타입 변수를 만드는 도구
- 함수에서는
Generic없이 TypeVar만으로 입력 타입과 반환 타입의 관계를 표현 가능
Generic
- 타입 변수를 클래스에 적용하는 도구
- 직접 만든 클래스도
list[int], dict[str, int]처럼 타입을 넣어 사용할 수 있게 해줌
Python 3.12+
class Box[T]:
def __init__(self, value: T):
self.value = value
def get_value(self) -> T:
return self.value
- Python 3.12버전 이상부터는
Generic과 TypeVar를 직접 선언하지 않고 타입 파라미터 문법으로 작성 가능
Protocol
기본
from typing import Protocol
class Drawable(Protocol):
def draw(self) -> None:
...
Protocol은 특정 메서드나 속성을 가진 객체를 구조적으로 판단하기 위한 기능
Protocol은 Duck Typing 스타일을 타입 힌트로 표현
- Duck Typing
- Python은 Duck Typing이라는 문화를 가진 언어
- 오리처럼 걷고 오리처럼 울면 클래스에 상관없이 오리로 취급한다
- 위 예제
Drawable타입으로 인정받으려면 draw 메서드를 가지고 있어야함
from typing import Protocol
class Drawable(Protocol):
def draw(self) -> None:
...
class Circle:
def draw(self) -> None:
print("원을 그림")
class Rectangle:
def draw(self) -> None:
print("사각형을 그림")
def render(obj: Drawable) -> None:
obj.draw()
render(Circle())
render(Rectangle())
원을 그림
사각형을 그림
Circle과 Rectangle 클래스는 Drawable을 상속받지 않았지만 draw 메서드가 있어 타입검사기에서도 문제없음
from typing import Protocol
class HasName(Protocol):
name: str
class User:
def __init__(self, name: str):
self.name = name
def print_name(obj: HasName) -> None:
print(obj.name)
print_name(User("Kim"))
Kim
ABC vs Protocol 비교
ABC: 이 인터페이스를 상속했는가?
- class A(ABC) 상속
- 런타임 추상 클래스, 강제 구현
- OOP 인터페이스
Protocol: 필요한 메서드와 속성을 가지고 있는가?
- class A(Protocol) 정의
- 상속하지 않아도 구조가 같으면 됨
- 정적 타입 검사에서 구조 표현
- Duck Typing에 가까움
runtime_checkable
from typing import Protocol, runtime_checkable
@runtime_checkable
class HasName(Protocol):
name: str
class User:
def __init__(self, name: str):
self.name = name
print(isinstance(User("kim"), HasName))
True
runtime_checkable
Protocol은 정적 타입 검사기용
Protocol은 isinstance()에 사용 불가
runtime_checkable은 Protocol에 isinstance()를 사용할 수 있게 해줌
- 런타임에서 메서드와 속성의 존재 여부를 확인
- 속성 타입, 메서드 인자 타입, 반환 타입까지 완벽하게 검증하지는 않음
Annotated
기본
from typing import Annotated
Age = Annotated[int, "0 이상의 정수여야함"]
def print_age(age: Age) -> None:
print(age)
Annotated는 기존 타입 힌트에 추가 정보(메타데이터)를 붙일 때 사용
- Python이 메타데이터를 자동으로 검사하지는 않음
- 실제 메타데이터 검증은 해석 도구나 라이브러리가 있어야 가능
FastAPI에서
from typing import Annotated
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items")
def read_items(q: Annotated[str | None, Query(max_length=50)] = None):
return {"q": q}
- FastAPI에서
Annotated 자주 쓰임
- FastAPI에서
Query, Path, Body, Depends와 같은 FastAPI 정보를 타입에 붙일 때 사용