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

함수 오버로딩 우회 — singledispatch / overload / match

~18 min · overloading, singledispatch, typing-overload, dispatch

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

Python 은 함수 오버로딩 없음 — 의도적

Java/C++ 는 같은 이름 + 다른 파라미터 타입의 여러 함수 선언 가능 — 컴파일러가 맞는 거 선택. Python 은 X. 같은 이름의 def f(...) 둘 정의하면 첫 번째 조용히 교체. Python 의 동적 타입 철학과 일관 — 정의 시점에 타입 모름.

functools.singledispatch — 첫 인자 타입 기반 dispatch

표준 라이브러리가 가장 흔한 경우용 singledispatch 제공 — 첫 인자 타입에 따라 dispatch. 일반 함수 데코레이트, 그 다음 타입별 특화 버전 등록. "같은 논리적 연산의 다른 형식" (이 dict 처리, 이 list 처리, 이 DataFrame 처리) 에 깔끔.

typing.overload — 정적 체크용 시그니처

@typing.overload 는 런타임 동작 전혀 안 바꿔. 순전히 타입 체커와 IDE 용 — 여러 시그니처 선언, 그 다음 진짜 구현 하나. 함수가 다른 반환 타입 만드는 여러 호출 모양 가질 때 유용. 순수 문서 + 도구 지원.

match — 구조 기반 런타임 dispatch

match 문 (flow 트랙에서) 이 값 타입 아닌 *구조* 기반 선택일 때 가장 깔끔한 런타임 dispatch. 모양 기반 오버로딩엔 match 가 종종 가장 읽기 좋은 답.

원칙: if isinstance(x, A): ... elif isinstance(x, B): ... 짜고 있으면 — 그게 타입 기반 오버로딩, singledispatch 가 보통 더 깔끔한 표현. 모양 기반 dispatch (3 개짜리 list vs 이 키 가진 dict) 면 match.

Code

singledispatch — 표준 라이브러리 답·python
from functools import singledispatch

@singledispatch
def serialize(obj):
    raise NotImplementedError(f"can't serialize {type(obj).__name__}")

@serialize.register
def _(obj: dict):
    return f"dict with {len(obj)} keys"

@serialize.register
def _(obj: list):
    return f"list of {len(obj)} items"

@serialize.register
def _(obj: str):
    return f"string of length {len(obj)}"

print(serialize({"a": 1}))           # dict with 1 keys
print(serialize([1, 2, 3]))         # list of 3 items
print(serialize("hello"))           # string of length 5
typing.overload — 타입 체커용 시그니처·python
from typing import overload

@overload
def parse(input: str) -> dict: ...
@overload
def parse(input: bytes) -> dict: ...
@overload
def parse(input: dict) -> dict: ...

def parse(input):                    # 진짜 구현
    if isinstance(input, str):
        import json
        return json.loads(input)
    if isinstance(input, bytes):
        return parse(input.decode())
    if isinstance(input, dict):
        return dict(input)
    raise TypeError("지원 X")

# 타입 체커는 세 시그니처 다 봐 — 런타임은 구현만
match — 구조 기반 dispatch·python
def process(message):
    match message:
        case {"type": "chat", "text": text}:
            return f"chat: {text}"
        case {"type": "command", "name": name, "args": [*args]}:
            return f"cmd: {name}({', '.join(map(str, args))})"
        case [first, *rest]:
            return f"list with first={first}, rest={rest}"
        case str() if len(message) < 100:
            return f"short string: {message}"
        case _:
            return "unhandled"

print(process({"type": "chat", "text": "hi"}))
print(process({"type": "command", "name": "add", "args": [1, 2]}))
print(process([10, 20, 30]))
print(process("abc"))
singledispatchmethod — 메서드 첫 인자 dispatch·python
from functools import singledispatchmethod

class Renderer:
    @singledispatchmethod
    def render(self, content):
        raise NotImplementedError

    @render.register
    def _(self, content: str):
        return f"<p>{content}</p>"

    @render.register
    def _(self, content: list):
        return "<ul>" + "".join(f"<li>{x}</li>" for x in content) + "</ul>"

r = Renderer()
print(r.render("hello"))             # <p>hello</p>
print(r.render(["a", "b", "c"]))     # <ul><li>a</li><li>b</li><li>c</li></ul>

External links

Exercise

singledispatcharea 함수 — 세 타입 처리 — dict {type: 'circle', radius: r} 가 πr² 반환, dict {type: 'rectangle', w, h} 가 w*h 반환, list [width, height, depth] 가 w*h*d (박스). 각 변형 별도 등록. 세 입력 모양 + 미지원 타입 테스트. match 문으로 다시 짜고 가독성 비교.

Progress

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

댓글 0

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

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