[Fluent Python] Ch 15 - @typing.overload

WonTerry·2026년 8월 8일

Python

목록 보기
22/28

동일한 함수명을 사용하는데 입력값의 타입 조합이 다양한 상황이라면 @overload 데커레이터를 사용하여 동일 이름을 가진 함수를 여러 개 작성한다는 말인가? 굳이 타입 조합이 다양한데 동일한 함수명을 적용하는 이유는 무엇인가? 실무 사례에 빈번한 일인가?

@typing.overload 예제

from typing import List, Union, overload

# 1. @overload를 이용한 타입 시그니처 정의
# 정수 리스트가 들어오면 정수를 반환한다고 명시
@overload
def my_sum(items: List[int]) -> int:
    ...

# 실수 리스트가 들어오면 실수를 반환한다고 명시
@overload
def my_sum(items: List[float]) -> float:
    ...

# 2. 실제 로직을 담당하는 구현체 (Implementation)
# @overload가 붙지 않은 마지막 함수가 실제 실행되는 함수입니다.
# 타입 힌트는 가장 포괄적인 형태를 사용합니다.
def my_sum(items: List[Union[int, float]]) -> Union[int, float]:
    total = 0
    for item in items:
        total += item
    return total

# --- 테스트 코드 ---

# 케이스 1: 정수 리스트
int_list = [1, 2, 3, 4]
result_int = my_sum(int_list)
print(f"결과: {result_int}, 타입: {type(result_int)}") 
# 결과: 10, 타입: <class 'int'> (타입 체커는 이를 int로 인식합니다)

# 케이스 2: 실수 리스트
float_list = [1.5, 2.5, 3.0]
result_float = my_sum(float_list)
print(f"결과: {result_float}, 타입: {type(result_float)}") 
# 결과: 7.0, 타입: <class 'float'> (타입 체커는 이를 float로 인식합니다)

# 케이스 3: 혼합된 리스트 (구현체 logic에 따라 동작)
mixed_list = [1, 2.5]
result_mixed = my_sum(mixed_list)
print(f"결과: {result_mixed}, 타입: {type(result_mixed)}") 
# 결과: 3.5, 타입: <class 'float'>

1. 동일한 함수명을 여러 개 작성한다는 것이 맞나요?

네, 맞습니다. 하지만 주의할 점이 있습니다.

코드를 작성할 때는 함수 이름이 같은 것을 여러 개 나열하지만, 실제로 컴퓨터가 실행할 때(Runtime)는 @overload가 붙은 함수들은 무시됩니다.

  • @overload가 붙은 함수들은 일종의 "설명서(Signature/Blueprint)"입니다.
  • 마지막에 @overload가 붙지 않은 실제 구현체(Implementation) 하나만 메모리에 남아서 실제로 실행됩니다.

따라서 "함수 이름을 여러 개 만든다"기보다는, "이 함수가 가질 수 있는 여러 가지 모습(형태)을 미리 선언해둔다"라고 이해하는 것이 정확합니다.


2. 왜 굳이 동일한 함수명을 사용하는가? (추상화와 다형성)

만약 @overload가 없다면, 개발자는 타입에 따라 함수 이름을 다르게 지어야 할까요?
예: sum_int(), sum_float(), sum_list() ...

이렇게 하면 다음과 같은 심각한 문제들이 발생합니다.

① 사용자 경험(API 사용성)의 저하 (Interface Consistency)

사용자 입장에서는 "숫자들을 합치고 싶다"는 목적은 하나입니다. 입력이 intfloat든 상관없이 그냥 sum()이라고 부르고 싶을 것입니다. 만약 타입마다 함수 이름이 다르다면, 사용자는 매번 "지금 내가 넣으려는 데이터 타입이 뭐지? 그럼 어떤 함수를 호출해야 하지?"를 고민하며 코드를 작성해야 합니다. 이는 매우 불편한 인터페이스입니다.

② 다형성(Polymorphism)의 원리

프로그래밍의 핵심 원칙 중 하나는 "동일한 동작(Interface)은 데이터의 종류(Type)에 상관없이 일관되게 수행되어야 한다"입니다. 이를 통해 우리는 코드를 훨씬 단순하게 만들 수 있습니다.

  • 나쁜 예 (함수명이 다를 때):
    if isinstance(data, int):
        result = sum_int(data)
    elif isinstance(data, float):
        result = sum_float(data)
  • 좋은 예 (함수명이 같을 때):
    # 사용자는 데이터가 뭔지 몰라도 그냥 sum()만 호출하면 됨
    result = sum(data) 

그럼 왜 @overload를 쓰나요?
위의 '좋은 예'처럼 함수명을 하나로 통일하면 사용하기는 편하지만, 타입 체커(mypy 등)는 "결과값이 int인지 float인지" 확신할 수 없게 됩니다.
@overload사용자에게는 편리한 '하나의 이름'을 제공하면서, 동시에 타입 검사기에게는 '정교한 규칙'을 알려주어 개발자의 실수를 방지하는 역할을 합니다.


3. 실무 사례에 빈번한 일인가?

네, 매우 빈번하며 특히 대규모 라이브러리 설계에서는 필수적입니다.

실무에서 우리가 사용하는 유명한 라이브러리들은 내부적으로 @overload를 엄청나게 많이 사용합니다.

  • NumPy / Pandas (데이터 과학):
    데이터가 int 배열인지, float 배열인지, 아니면 DataFrame인지에 따라 함수가 반환하는 결과의 타입이 완전히 달라집니다. 이때 사용자가 함수 이름을 매번 바꿀 수는 없으므로, @overload를 통해 "이런 타입이 들어오면 저런 타입이 나온다"라고 선언해둡니다.
  • Standard Library (Python 표준 라이브러리):
    Python의 json.loads() 같은 함수도 입력값의 타입에 따라 결과가 달라질 수 있는데, 이를 정교하게 정의할 때 사용됩니다.
  • Frameworks (FastAPI, Pydantic 등):
    입력되는 JSON 데이터의 구조에 따라 반환되는 객체의 타입이 결정되는 경우, 이를 정확히 추론하기 위해 @overload를 적극적으로 사용합니다.

요약

구분@overload 사용 안 함 (Union 사용)@overload 사용 (동일 이름)
사용자 편의성높음 (함수 하나만 쓰면 됨)높음 (함수 하나만 쓰면 됨)
타입 안전성낮음 (결과 타입을 모호하게 인식)높음 (결과 타입을 정확히 예측)
설계 의도"이거 아니면 저거 나올 거야" (뭉뚱그림)"이게 오면 이건 이거야" (정교함)

결론적으로, @overload"사용자에게는 편리함(단일 함수명)을 주고, 개발자에게는 안전함(정확한 타입 검사)을 주는" 아주 똑똑한 설계 도구입니다.

profile
Hello, I'm Terry! 👋 Enjoy every moment of your life! 🌱 My current interests are Signal processing, Machine learning, Python, Database, LLM & RAG, MCP & ADK, Multi-Agents, Physical AI, ROS2...

0개의 댓글