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

인자를 받는 데코레이터는 세 겹이야

~22 min · decorator-factory, parameterized, @deco(args), closure

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

설정, 함수, 호출 인자를 차례로 받는다

@retry(times=3)은 먼저 설정을 받는 팩토리를 호출하고, 그 결과가 함수를 받는 데코레이터가 되며, 마지막 래퍼가 실제 호출 인자를 받아. 세 단계는 각각 서로 다른 수명과 책임을 가져.

설정은 클로저에 남는다

팩토리 호출마다 별도 클로저가 만들어지므로 서로 다른 제한과 상태를 가진 데코레이터를 독립적으로 쓸 수 있어. 괄호를 빼면 팩토리에 설정 대신 함수 자체가 들어가 계약이 달라져.

원칙: 바깥에서 안으로 “설정 → 꾸밀 함수 → 호출 값” 순서로 읽으면 세 겹이 헷갈리지 않아.

Code

설정·함수·호출의 세 단계·python
import functools
import time

def retry(times):                     # 레벨 1: 설정 받음
    def decorator(fn):                 # 레벨 2: 함수 받음
        @functools.wraps(fn)
        def wrapper(*args, **kwargs):  # 레벨 3: 일 함
            last_err = None
            for attempt in range(times):
                try:
                    return fn(*args, **kwargs)
                except Exception as e:
                    last_err = e
                    print(f"attempt {attempt+1} failed: {e}")
            raise last_err
        return wrapper
    return decorator

@retry(times=3)
def flaky():
    import random
    if random.random() < 0.7:
        raise RuntimeError("flake")
    return "ok"

# 호출하면 flake 시 최대 3 번 재시도
괄호를 빠뜨렸을 때 생기는 일·python
import functools

def cache(maxsize=None):              # factory
    def decorator(fn):
        @functools.wraps(fn)
        def wrapper(*args, **kwargs):
            return fn(*args, **kwargs)
        return wrapper
    return decorator

# 맞는 방법
@cache()                              # 인자 없어도 괄호
def good():
    pass

# 잘못된 방법 — 흔한 버그
try:
    @cache                            # 괄호가 없어서 'good' 을 maxsize 로 넘김
    def bad():
        pass
    bad()
except TypeError as e:
    print("error:", e)
설정할 수 있는 호출 기록·python
import functools

def logged(level="INFO", include_args=True):
    def decorator(fn):
        @functools.wraps(fn)
        def wrapper(*args, **kwargs):
            if include_args:
                arglist = f"args={args}, kwargs={kwargs}"
            else:
                arglist = ""
            print(f"[{level}] {fn.__name__} {arglist}")
            return fn(*args, **kwargs)
        return wrapper
    return decorator

@logged(level="DEBUG", include_args=True)
def compute(x, y):
    return x + y

@logged(level="WARN", include_args=False)
def run():
    return "done"

compute(3, 4)
run()
클로저마다 독립적으로 남는 설정·python
import functools

def tagged(prefix):
    def decorator(fn):
        @functools.wraps(fn)
        def wrapper(*args, **kwargs):
            return f"[{prefix}] {fn(*args, **kwargs)}"
        return wrapper
    return decorator

@tagged("INFO")
def info_msg(s):
    return s

@tagged("ERROR")
def err_msg(s):
    return s

print(info_msg("hello"))   # '[INFO] hello'
print(err_msg("oops"))     # '[ERROR] oops'

# 각 데코레이션이 closure 통해 자기 prefix 캡쳐

External links

Exercise

cap(max_calls)를 만들어 지정 횟수 뒤에는 RuntimeError('call limit reached')를 내게 해. 한 함수에는 3회, 다른 함수에는 1회 제한을 붙이고 각각 넘겨 호출해. functools.wraps와 클로저를 사용해 횟수가 서로 섞이지 않게 해.

Progress

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

댓글 0

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

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