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

Decorator Factory — 인자 받는 decorator

~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

3 단계 구조

일반 decorator 는 2 단계 — 바깥 함수가 wrapped 함수 받고, 안의 wrapper 가 일 함. 인자 받는 decorator (@retry(times=3) 처럼 짜는) 는 *3* 단계 — 바깥 함수가 인자 받고, 일반 decorator 반환, 그게 wrapper 반환. 첫 단계가 "decorator factory" — 설정에 따라 decorator 제조.

왜 3 단계 — 각 단계가 뭐 하나

레벨 1 — factory 함수 — 설정 인자 받음 (retry(times=3)). 레벨 2 반환. 레벨 2 — 실제 decorator — 감쌀 함수 받음. 레벨 3 반환. 레벨 3 — wrapper — *args, **kwargs 받고 일 함. 3 단계 이유 — @retry(times=3) 가 "retry(times=3) 호출, 그 결과를 decorator 로 적용" 의미.

Closure 가 설정 보관

레벨 3 의 wrapper 는 레벨 1 의 times=3 알아야 해. 둘러싼 scope 통해 접근 — closure. factory 가 호출될 때마다 새 decorator 만들어서 각 호출 사이트마다 다른 설정 정확히 캡쳐.

War Story: decorator factory 의 가장 흔한 버그 — 괄호 없이 호출. @retry 가 감쌀 함수를 times 인자로 사용하려 함. 에러 메시지 헷갈려. 해결책 항상 — 괄호, 인자 안 넘겨도 — @retry().

옵션 인자 트릭 — 그리고 안 쓸 때

일부 decorator 는 @deco@deco(args) 둘 다 지원하려 함. 첫 인자가 함수 (인자 없는 경우) 인지 다른 거 (factory 경우) 인지 체크 포함. 영리하지만 Python decorator 코드의 큰 혼란 원인 중 하나. 야생의 잘 디자인된 decorator 는 무조건 괄호 요구 — 디폴트 설정의 @retry() 가 명료함의 대가.

Code

Decorator factory — 3 단계·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                            # 괄호 X — '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()
Closure 가 설정 캡쳐 — 다른 설정 독립 작동·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

인자 받는 decorator cap(max_calls) 작성 — wrapped 함수의 호출 횟수 제한. max_calls 호출 후 호출하면 RuntimeError("call limit reached"). 한 함수에 @cap(max_calls=3), 다른 함수에 @cap(max_calls=1) 적용, 각각 충분히 호출해서 limit 트리거. functools.wraps 사용. closure scope 로 데코레이션마다 카운트.

Progress

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

댓글 0

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

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