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

데코레이터 — 함수를 받아 새 함수로 돌려주기

~22 min · decorator, wrapper, first-class, @

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

@ 아래에는 평범한 함수 호출이 있어

@deco를 붙인 함수는 정의가 끝난 뒤 f = deco(f)로 다시 묶여. 데코레이터는 함수를 받아 호출 가능한 값을 돌려주는 변환일 뿐이야.

래퍼는 호출 계약을 그대로 전달해

래퍼는 보통 *args, **kwargs를 받아 원래 함수에 넘기고 결과를 그대로 돌려줘. 그 앞뒤에 기록, 시간 측정, 권한 확인, 캐시 같은 공통 동작을 넣을 수 있어.

여러 개를 쌓으면 아래부터 적용돼

가장 함수에 가까운 데코레이터가 먼저 함수를 감싸고, 그 결과를 위의 데코레이터가 다시 감싸. 호출 시에는 바깥쪽부터 들어가므로 적용 순서와 실행 순서를 구분해야 해.

원칙: 핵심 동작을 바꾸지 않고 여러 함수에 같은 주변 책임을 붙일 때 데코레이터를 써.

Code

@deco를 평범한 대입으로 풀어 보기·python
def announce(fn):
    def wrapper():
        print("calling", fn.__name__)
        result = fn()
        print("finished")
        return result
    return wrapper

# 둘은 *정확히* 같음:

# 형태 1 — 수동
def hello():
    print("hi")
hello = announce(hello)

# 형태 2 — sugar
@announce
def hello():
    print("hi")

hello()
# calling hello
# hi
# finished
모든 인자를 전달하는 래퍼·python
import time

def timed(fn):
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = fn(*args, **kwargs)
        elapsed = time.perf_counter() - start
        print(f"{fn.__name__} took {elapsed*1000:.2f}ms")
        return result
    return wrapper

@timed
def slow_add(a, b):
    time.sleep(0.1)
    return a + b

print(slow_add(3, 4))
# slow_add took ~100ms
# 7

# 어떤 시그니처와도 작동 — *args/**kwargs forwarding 덕분
@timed
def greet(name, greeting="Hello"):
    return f"{greeting}, {name}"

print(greet("Pippa", greeting="Hi"))
호출을 기록하는 데코레이터·python
def log_calls(fn):
    def wrapper(*args, **kwargs):
        arglist = ", ".join(
            [repr(a) for a in args] + [f"{k}={v!r}" for k, v in kwargs.items()]
        )
        print(f"call: {fn.__name__}({arglist})")
        result = fn(*args, **kwargs)
        print(f"  -> {result!r}")
        return result
    return wrapper

@log_calls
def add(a, b):
    return a + b

add(3, 4)
# call: add(3, 4)
#   -> 7

add(b=10, a=2)
# call: add(a=2, b=10)
#   -> 12
쌓인 데코레이터의 적용 순서·python
def shout(fn):
    def wrapper(*args, **kwargs):
        return fn(*args, **kwargs).upper()
    return wrapper

def exclaim(fn):
    def wrapper(*args, **kwargs):
        return fn(*args, **kwargs) + "!"
    return wrapper

# @shout 마지막 적용 — 호출 사이트에 가까움
# @exclaim 처음 적용 — 함수에 가까움
@shout
@exclaim
def greet(name):
    return f"hello {name}"

print(greet("pippa"))   # 'HELLO PIPPA!'

# greet = shout(exclaim(greet)) 와 같음
# 1. exclaim 이 greet 감싸 '!' 추가
# 2. shout 이 그걸 감싸 대문자로

External links

Exercise

함수 호출 횟수를 세는 count_calls(fn) 데코레이터를 만들어. 원래 결과를 그대로 돌려주고 횟수는 wrapped_fn.calls로 읽게 해. 서로 다른 두 함수에 붙여 각자의 횟수가 따로 유지되는지 확인해.

Progress

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

댓글 0

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

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