TypedDict: 딕셔너리에 구조를 부여하기

TypedDict로 딕셔너리의 키와 값 타입을 고정하고, 선택적 키·total 옵션·중첩 구조까지 다루며 JSON 데이터를 안전하게 처리하는 법을 완전 정복합니다.

· 4 min read · PALDYN Team

지난 글에서 값과 불변을 타입으로 제약하는 법을 배웠다. 실무에서 우리는 JSON 응답이나 설정처럼 “정해진 키를 가진 딕셔너리”를 자주 다룬다. dict[str, Any]라고 힌트를 달면 타입 검사가 사실상 무력해진다. 키 이름을 오타 내도, 값 타입이 틀려도 검사기가 잡지 못한다. TypedDict는 딕셔너리에 구조를 부여해 이 문제를 해결한다.

평범한 dict의 한계

먼저 TypedDict 없이 딕셔너리를 다룰 때 무엇이 문제인지 보자.

def make_user() -> dict:
    return {"name": "Sam", "age": 30}

u = make_user()
print(u["nmae"])   # 오타! 그래도 검사기는 통과시킨다
print(u["age"] + "년")  # 타입 오류지만 잡지 못한다

dict는 어떤 키가 있는지, 각 값이 무슨 타입인지 검사기에 알려 주지 않는다. 그래서 오타와 타입 실수가 런타임까지 살아남는다.

TypedDict 정의하기

TypedDict를 상속해 클래스처럼 키와 값 타입을 적으면, 검사기가 그 구조를 강제한다.

from typing import TypedDict

class User(TypedDict):
    name: str
    age: int

u: User = {"name": "Sam", "age": 30}
print(u["nmae"])    # 오류: "nmae"라는 키는 없다
print(u["age"] + 1) # OK: age는 int로 알려져 있다

TypedDict — dict에 키 구조를 부여

중요한 점은 User가 진짜 클래스가 아니라는 것이다. 런타임에 u는 그냥 평범한 dict다. TypedDict는 검사기에만 존재하는 구조 정보일 뿐, 인스턴스를 만들거나 메서드를 갖지 않는다.

선택적 키

모든 키가 항상 존재하진 않는다. NotRequired로 특정 키를 선택적으로 표시할 수 있다.

from typing import TypedDict, NotRequired

class User(TypedDict):
    name: str
    age: int
    email: NotRequired[str]   # 있어도 없어도 됨

a: User = {"name": "Sam", "age": 30}              # OK
b: User = {"name": "Sue", "age": 25, "email": "x@y.z"}  # OK
c: User = {"name": "Tom"}    # 오류: age 누락

TypedDict 정의와 선택적 키

반대로 대부분의 키가 선택적이라면 total=False로 정의 전체를 선택적으로 만들고, 필수 키만 Required로 표시할 수도 있다.

from typing import TypedDict, Required

class Config(TypedDict, total=False):
    host: Required[str]   # 이것만 필수
    port: int
    debug: bool

중첩과 재사용

TypedDict는 서로 중첩할 수 있어, 복잡한 JSON 구조를 그대로 모델링하기 좋다.

from typing import TypedDict

class Address(TypedDict):
    city: str
    zipcode: str

class Person(TypedDict):
    name: str
    address: Address    # 중첩 TypedDict

p: Person = {
    "name": "Sam",
    "address": {"city": "Seoul", "zipcode": "04524"},
}

p["address"]["city"]에 접근할 때 검사기는 city가 문자열임을 안다. API 응답을 다룰 때 이 중첩 구조가 큰 힘을 발휘한다.

TypedDict vs dataclass

비슷해 보이지만 쓰임이 다르다. TypedDict이미 딕셔너리 형태인 데이터(JSON 파싱 결과, 외부 API 응답)에 타입을 입힐 때 적합하다. 새로운 객체를 설계한다면 dataclass가 더 낫다. dataclass는 진짜 클래스라 메서드·기본값·검증을 가질 수 있지만, TypedDict는 런타임 오버헤드가 전혀 없고 기존 dict 코드와 자연스럽게 섞인다. 정리하면, “딕셔너리를 dict인 채로 안전하게” 쓰고 싶을 때 TypedDict를 선택한다. 다음 글에서는 상속 없이 인터페이스를 정의하는 Protocol을 살펴본다.


지난 글: Literal과 Final: 값과 불변을 타입으로 표현하기

다음 글: typing.Protocol: 상속 없는 구조적 타이핑


읽어주셔서 감사합니다. 😊