Wooks_learning

[Python] typing — Optional, Union, List, Dict, Tuple, Set 본문

Computer science/Python

[Python] typing — Optional, Union, List, Dict, Tuple, Set

Wooks_ 2026. 3. 24. 18:42
반응형

타입 힌트를 쓰면 코드가 명확해지고, 실수를 줄일 수 있다.
하지만 typing 모듈을 처음 접하면 언제 뭘 써야 할지 헷갈린다.
이 글에서 자주 쓰이는 핵심 6가지를 예제 중심으로 정리한다.


typing이란?

Python은 기본적으로 동적 타입 언어다. 변수에 어떤 타입의 값을 넣어도 런타임에 오류가 나지 않는다.

x = 10
x = "hello"  # 오류 없음

하지만 코드가 커질수록 이 유연함이 오히려 독이 된다.
"이 함수가 뭘 받고 뭘 반환하는지"를 코드만 보고 파악하기 어려워지기 때문이다.

typing 모듈은 이 문제를 해결하기 위해 Python 3.5에서 도입됐다.
타입을 강제하지는 않지만, 힌트를 명시함으로써 가독성을 높이고 정적 분석 도구(mypy, pylance 등)와 IDE의 자동완성을 지원한다.

중요: 타입 힌트는 런타임에 강제되지 않는다.
잘못된 타입을 넘겨도 Python은 오류를 내지 않는다.
단, mypy 같은 정적 분석 도구를 쓰면 실행 전에 타입 오류를 잡을 수 있다.


1. Optional — None이 될 수 있는 값

개념

Optional[X]는 "X 타입이거나 None일 수 있다" 는 의미다.
값이 존재하지 않을 수도 있는 상황에 사용한다.

from typing import Optional

Optional[str]  # str 또는 None

내부적으로는 Union[X, None]과 완전히 동일하다.

예제

from typing import Optional

# 유저를 찾으면 이름을 반환하고, 없으면 None을 반환
def find_user(user_id: int) -> Optional[str]:
    if user_id == 1:
        return "Alice"
    return None

# 인자의 기본값이 None인 경우
def connect(host: str, port: Optional[int] = None) -> None:
    if port is None:
        port = 8080
    print(f"Connecting to {host}:{port}")

주의

Optional을 쓴다고 해서 None 처리를 자동으로 해주지는 않는다.
반드시 코드 안에서 None 여부를 직접 확인해야 한다.

def greet(name: Optional[str]) -> str:
    if name is None:          # None 체크 필수
        return "Hello, stranger!"
    return f"Hello, {name}!"

Python 3.10+ 대안

# 아래 두 표현은 동일하다
def find_user(user_id: int) -> Optional[str]: ...
def find_user(user_id: int) -> str | None: ...   # 3.10+ 권장

2. Union — 여러 타입 중 하나

개념

Union[X, Y]는 "X 또는 Y 타입" 을 의미한다.
두 가지 이상의 타입을 허용해야 할 때 사용한다.

from typing import Union

Union[int, str]        # int 또는 str
Union[int, str, None]  # int 또는 str 또는 None (= Optional[Union[int, str]])

예제

from typing import Union

# 숫자 또는 문자열을 받아서 처리
def process(value: Union[int, str]) -> str:
    if isinstance(value, int):
        return f"숫자: {value * 2}"
    return f"문자열: {value.upper()}"

print(process(10))       # 숫자: 20
print(process("hello"))  # 문자열: HELLO

Optional과의 관계

# 아래 두 표현은 완전히 동일하다
Optional[str]
Union[str, None]

Optional은 Union[X, None]의 축약 표현일 뿐이다.

Python 3.10+ 대안

# 아래 두 표현은 동일하다
def process(value: Union[int, str]) -> str: ...
def process(value: int | str) -> str: ...   # 3.10+ 권장

3. List — 리스트의 원소 타입 명시

개념

List[X]는 "X 타입 원소들로 이루어진 리스트" 를 의미한다.

from typing import List

List[int]   # 정수 리스트
List[str]   # 문자열 리스트

예제

from typing import List

def get_scores(students: List[str]) -> List[int]:
    score_map = {"Alice": 90, "Bob": 85, "Charlie": 92}
    return [score_map.get(s, 0) for s in students]

scores = get_scores(["Alice", "Bob"])
print(scores)  # [90, 85]

중첩 사용

from typing import List

# 리스트 안에 리스트
matrix: List[List[int]] = [
    [1, 2, 3],
    [4, 5, 6],
]

Python 3.9+ 대안

# 아래 두 표현은 동일하다
def foo(items: List[int]) -> None: ...
def foo(items: list[int]) -> None: ...   # 3.9+ 부터 소문자 list 사용 가능

4. Dict — 딕셔너리의 키/값 타입 명시

개념

Dict[K, V]는 "K 타입의 키와 V 타입의 값으로 이루어진 딕셔너리" 를 의미한다.

from typing import Dict

Dict[str, int]    # 문자열 키, 정수 값
Dict[int, str]    # 정수 키, 문자열 값

예제

from typing import Dict, List

def count_words(words: List[str]) -> Dict[str, int]:
    result: Dict[str, int] = {}
    for word in words:
        result[word] = result.get(word, 0) + 1
    return result

print(count_words(["apple", "banana", "apple"]))
# {'apple': 2, 'banana': 1}

중첩 사용

from typing import Dict, List

# 유저별 점수 목록
user_scores: Dict[str, List[int]] = {
    "Alice": [90, 85, 92],
    "Bob":   [78, 88, 95],
}

Python 3.9+ 대안

# 아래 두 표현은 동일하다
def foo(data: Dict[str, int]) -> None: ...
def foo(data: dict[str, int]) -> None: ...   # 3.9+ 부터 소문자 dict 사용 가능

5. Tuple — 고정 길이, 고정 타입의 순서쌍

개념

Tuple[X, Y, Z]는 "첫 번째 원소는 X, 두 번째는 Y, 세 번째는 Z 타입인 튜플" 을 의미한다.
List와 달리 길이와 각 위치의 타입이 고정된다.

from typing import Tuple

Tuple[str, int]        # (이름, 나이) 같은 구조
Tuple[int, int, int]   # (x, y, z) 좌표 같은 구조

예제

from typing import Tuple

# 이름과 나이를 함께 반환
def get_user_info(user_id: int) -> Tuple[str, int]:
    return ("Alice", 30)

name, age = get_user_info(1)
print(f"{name}은 {age}세입니다.")  # Alice은 30세입니다.

가변 길이 Tuple

길이가 정해지지 않은 같은 타입의 튜플은 ...을 사용한다.

from typing import Tuple

# 정수만 담긴 튜플 (길이 무관)
def sum_all(values: Tuple[int, ...]) -> int:
    return sum(values)

print(sum_all((1, 2, 3, 4, 5)))  # 15

Python 3.9+ 대안

# 아래 두 표현은 동일하다
def foo() -> Tuple[str, int]: ...
def foo() -> tuple[str, int]: ...   # 3.9+ 부터 소문자 tuple 사용 가능

6. Set — 집합의 원소 타입 명시

개념

Set[X]는 "X 타입의 원소들로 이루어진 집합" 을 의미한다.
List와 비슷하지만 중복을 허용하지 않고 순서가 없다는 점이 다르다.

from typing import Set

Set[int]   # 정수 집합
Set[str]   # 문자열 집합

예제

from typing import Set, List

# 중복 제거 후 고유한 태그 목록 반환
def get_unique_tags(posts: List[List[str]]) -> Set[str]:
    result: Set[str] = set()
    for tags in posts:
        result.update(tags)
    return result

tags = get_unique_tags([
    ["python", "backend"],
    ["python", "typing"],
    ["backend", "api"],
])
print(tags)  # {'python', 'backend', 'typing', 'api'}

Python 3.9+ 대안

# 아래 두 표현은 동일하다
def foo(items: Set[str]) -> None: ...
def foo(items: set[str]) -> None: ...   # 3.9+ 부터 소문자 set 사용 가능

전체 요약

타입 의미 예시 3.9+ 대안

Optional[X] X 또는 None Optional[str] str | None
Union[X, Y] X 또는 Y Union[int, str] int | str
List[X] X 타입의 리스트 List[int] list[int]
Dict[K, V] K키 V값 딕셔너리 Dict[str, int] dict[str, int]
Tuple[X, Y] 고정 길이 순서쌍 Tuple[str, int] tuple[str, int]
Set[X] X 타입의 집합 Set[str] set[str]

버전별 권장 사용법 정리

# Python 3.8 이하 — typing 모듈 필수
from typing import List, Dict, Tuple, Set, Optional, Union

def foo(
    names: List[str],
    scores: Dict[str, int],
    point: Tuple[int, int],
    tags: Set[str],
    nickname: Optional[str],
    value: Union[int, str],
) -> None:
    ...

# Python 3.9 이상 — 소문자 내장 타입 사용 가능 (List, Dict 등 불필요)
# Python 3.10 이상 — | 연산자로 Union, Optional 대체 가능
def foo(
    names: list[str],
    scores: dict[str, int],
    point: tuple[int, int],
    tags: set[str],
    nickname: str | None,
    value: int | str,
) -> None:
    ...

마치며

타입 힌트는 처음에는 "그냥 주석 아닌가?" 싶을 수 있다.
하지만 코드가 커지고 협업이 시작되면, 타입 힌트가 있고 없고의 차이가 크게 느껴진다.

특히 Optional로 None 가능성을 명시하거나, Dict[str, List[int]]처럼 중첩 구조를 표현하면 함수의 동작을 문서 없이도 바로 파악할 수 있다.

당장 모든 코드에 적용하기 어렵다면, 함수의 인자와 반환값부터 타입 힌트를 붙이는 습관을 들여보자.


참고

반응형

'Computer science > Python' 카테고리의 다른 글

[Python] Call by value와 call by reference  (0) 2024.12.05
Comments