파이썬193번5 MIN
Union과 Optional: 여러 타입을 허용하기
Union, 파이프 신문법, Optional의 정확한 의미와 타입 좁히기(narrowing)까지, 여러 타입을 안전하게 다루는 법을 완전 정복합니다.
이 글은 PALDYN Tech Blog의 글입니다.원문 보기
지난 글에서 제네릭으로 타입을 흘려보내는 법을 배웠다. 현실의 함수는 종종 "정수 아니면 문자열", "값 아니면 None"처럼 여러 타입을 받는다. 파이썬에서 이 "여러 타입 중 하나"를 표현하는 도구가 Union이고, 그중에서도 가장 흔한 "None일 수도 있음"을 위한 약칭이 Optional이다.
Union: 여러 타입 중 하나
Union[A, B]는 "A 또는 B 타입의 값"을 뜻한다. 함수가 여러 형태의 입력을 받거나, 상황에 따라 다른 타입을 반환할 때 쓴다.
from typing import Union
def to_int(x: Union[int, str]) -> int:
if isinstance(x, str):
return int(x)
return x
to_int(42) # OK
to_int("42") # OK
to_int(3.14) # 오류: float은 허용 안 됨
x는 정수이거나 문자열이다. 그 외의 타입을 넘기면 검사기가 거부한다.
파이프 신문법
파이썬 3.10부터는 Union 대신 파이프(|)로 더 짧게 쓸 수 있다. import도 필요 없다. 의미는 완전히 동일하다.
def to_int(x: int | str) -> int:
if isinstance(x, str):
return int(x)
return x
int | str은 Union[int, str]과 같다. 최신 코드에서는 이 파이프 문법이 표준으로 자리 잡았다. 가독성이 좋고 타이핑할 글자도 적다.
Optional의 정확한 의미
Optional[int]는 많은 사람이 "선택적 인자", 즉 "안 넘겨도 되는 인자"로 오해한다. 하지만 정확한 의미는 int | None, 즉 "정수이거나 None일 수 있다"이다. 인자를 생략 가능하게 만드는 것은 기본값(= None)이지 Optional 자체가 아니다.
from typing import Optional
# 이 둘은 완전히 같다
def f(x: Optional[int]) -> None: ...
def f(x: int | None) -> None: ...
# Optional이라고 자동으로 생략 가능한 게 아니다
def g(x: Optional[int]) -> None: ...
g() # 오류: x는 여전히 필수 인자
g(None) # OK
생략 가능하게 하려면 기본값을 줘야 한다: def f(x: int | None = None). Optional은 "값의 종류"를, 기본값은 "인자의 필수 여부"를 결정한다. 둘은 별개다.
타입 좁히기(narrowing)
Union 타입을 받으면 그대로는 각 타입의 메서드를 쓸 수 없다. 검사기가 "둘 중 무엇인지 모르니" 안전한 메서드만 허용하기 때문이다. isinstance, is None 같은 검사를 통과하면 검사기가 그 블록 안에서 타입을 좁혀 준다.
def length(s: str | None) -> int:
if s is None:
return 0
# 이 줄부터 검사기는 s를 str로 좁힌다
return len(s)
length("hello") # 5
length(None) # 0
if s is None: 블록에서 일찍 반환하면, 그 이후 코드에서 s는 자동으로 str로 간주된다. 이런 "조기 반환 후 좁히기" 패턴은 None을 다루는 가장 깔끔한 방법이다.
좁히기를 돕는 도구들
isinstance와 is None 외에도 좁히기를 유도하는 방법이 여럿 있다.
from typing import assert_never
def handle(x: int | str | bytes) -> str:
if isinstance(x, int):
return str(x)
elif isinstance(x, str):
return x
elif isinstance(x, bytes):
return x.decode()
else:
assert_never(x) # 모든 경우를 처리했는지 검사기가 확인
assert_never는 모든 분기를 처리했는지 검사기가 확인하게 해 준다. 나중에 Union에 타입을 추가하면, 처리하지 않은 분기가 있을 때 검사기가 경고를 띄운다. 다음 글에서는 값 자체를 타입으로 고정하는 Literal과 재할당을 막는 Final을 다룬다.
지난 글: 제네릭: TypeVar와 Generic으로 타입을 재사용하기
다음 글: Literal과 Final: 값과 불변을 타입으로 표현하기
읽어주셔서 감사합니다. 😊

