C.W.K.
Stream
Lesson 02 of 05 · published

Literal / Final / ClassVar — 정밀한 제약

~18 min · literal, final, classvar, annotated

Level 0호기심
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete

Literal — 정확한 값, 단순 타입 X

가끔 "문자열" 이 충분히 정밀하지 않아 — "정확히 이 문자열들 중 하나" 말하고 싶음. 파라미터 타입으로 Literal["red", "green", "blue"] 가 타입 체커가 그 셋만 받게. status enum-as-string, mode 플래그, 고정 set 에서 선택하는 모든 거에 유용.

Final — 재할당 X

Final 이 변수, 속성, 클래스가 재할당 또는 서브클래싱 의도 X 라고 타입 체커한테. MAX_SIZE: Final = 100. 모듈 후반 MAX_SIZE = 200 이 타입 에러. 상수와 sentinel 에 유용 — 의도 전달 + 타입 체커 강제 가능.

ClassVar — 클래스 속성, 인스턴스 X

클래스 본문에서 x: int 가 디폴트로 인스턴스 속성 (모든 인스턴스 가짐) 처리. x: ClassVar[int] 가 클래스 속성 (인스턴스 간 공유) 라고 타입 체커한테. dataclass 에서 특히 중요 — 클래스 레벨 상수 vs 인스턴스별 필드 구분 의미 있음.

Annotated — 타입에 메타데이터 부착

Annotated[int, "the user id"] 가 런타임에 int 와 같은 타입, 메타데이터 부착. Pydantic, FastAPI, 검증 라이브러리들이 새 타입 alias 안 만들고 제약 (min/max, regex 등) 인코딩에 많이 사용.

Pythonic Way: 문자열 또는 int 가 사실 고정 set 의 하나일 때 Literal 손에 닿아. 오타 ("warning" 대신 "warming") 에서 받는 타입 체커 에러가 진짜 버그 방지 승리. 상수의 Final 이 리팩터 실수 시끄럽게.

Code

Literal — 정확한 값·python
from typing import Literal

Mode = Literal["r", "w", "a", "r+"]

def open_file(path: str, mode: Mode = "r") -> str:
    return f"opening {path} in {mode}"

open_file("x.txt", "r")     # OK
open_file("x.txt", "w")     # OK
# open_file("x.txt", "x")   # mypy 에러: Literal['r', 'w', 'a', 'r+'] 기대

# Literal 이 int, bool, None 에도 작동
LogLevel = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
MaybeOn = Literal[True, False]
Final — 재할당 불가·python
from typing import Final

# 모듈 레벨 상수
MAX_RETRIES: Final = 3
# MAX_RETRIES = 5     # mypy 에러: final 이름에 할당 불가

# 클래스 속성
class Config:
    VERSION: Final = "1.0.0"
    TIMEOUT: Final[int] = 30

# 서브클래싱도 표시
# class SubConfig(Config):
#     VERSION = "2.0"   # mypy 에러: final 속성 override 불가

# sentinel, 설정 상수, 의도상 immutable 한 모든 것에 유용
ClassVar — 클래스 속성 vs 인스턴스·python
from typing import ClassVar
from dataclasses import dataclass

@dataclass
class User:
    name: str                          # 인스턴스 속성
    age: int                           # 인스턴스 속성
    DEFAULT_ROLE: ClassVar[str] = "member"   # 클래스 속성, 공유

u = User("alice", 30)
print(u.name)
print(u.DEFAULT_ROLE)                   # 'member' — 인스턴스로 접근
print(User.DEFAULT_ROLE)                # 클래스로도

# ClassVar 없으면 dataclass 가 DEFAULT_ROLE 을 인스턴스 필드로 처리
# + __init__ 에 요구 — 잘못된 동작.
Annotated — 타입 + 메타데이터·python
from typing import Annotated

# 그냥 int — 추가 정보 X
def plain(x: int) -> int:
    return x

# 제약 가진 Annotated int
UserId = Annotated[int, "사용자 유니크 식별자", "양의 정수"]

def with_meta(uid: UserId) -> str:
    return f"user {uid}"

# 런타임에 UserId 는 그냥 int
print(with_meta(42))                    # 'user 42'

# 메타데이터는 typing.get_type_hints / typing.get_args 로 접근
from typing import get_type_hints, get_args
hints = get_type_hints(with_meta, include_extras=True)
print(hints["uid"])
print(get_args(hints["uid"]))

External links

Exercise

LogLevel = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] 정의. 함수 log(message: str, level: LogLevel = "INFO") 가 그냥 print. 그 다음 클래스 ConfigFinal 속성 VERSION: Final = "1.0.0" + ClassVar[int] MAX_RETRIES: ClassVar[int] = 3. VERSION 재할당 시도 또는 미지원 log level 넘기면 타입 체커 불평하는 거 보여.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.