본문 바로가기
C.W.K.
Stream
Lesson 03 of 05 · published

Python에서 여러 구현을 고르는 세 방법

~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

같은 이름의 함수를 여러 번 정의해도 오버로딩되지 않아

뒤의 정의가 앞의 정의를 덮으므로, 무엇을 기준으로 구현을 고를지 명시해야 해.

타입 기준이면 singledispatch

functools.singledispatch는 첫 인자의 런타임 타입에 따라 등록된 구현을 골라. 메서드에는 singledispatchmethod가 같은 역할을 해.

정적 계약이면 overload

typing.overload는 타입 검사기와 IDE에 여러 시그니처를 보여주지만 실행 시에는 하나의 실제 구현만 있어야 해.

값의 모양이면 match

딕셔너리 키, 시퀀스 길이, 리터럴처럼 구조와 값으로 나눈다면 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
타입 검사기에 여러 시그니처 알리기·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("지원하지 않음")

# 타입 체커는 세 시그니처 다 봐 — 런타임은 구현만
값의 구조로 나누는 match·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·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

singledispatch로 area를 만들어 dict 원과 직사각형, [width, height, depth] 리스트 상자를 처리하고 지원하지 않는 타입도 시험해. 같은 입력 구분을 match로 다시 작성해 타입 기준과 구조 기준 중 어느 쪽이 더 잘 맞는지 비교해.

Progress

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

댓글 0

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

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