Python 인자를 가지는 데코레이터: 데코레이터에 값을 전달하는 방법

Ad Placeholder

앞에서는 데코레이터에 함수의 인자와 반환값을 전달하는 방법을 알아봤습니다.

def decorator(func):

    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result

    return wrapper

이제 한 단계 더 나아가 보겠습니다.

이번에는 함수뿐만 아니라 데코레이터 자체에도 원하는 값을 전달하는 방법입니다.

예를 들어 다음과 같이 사용하고 싶을 수 있습니다.

@log("DEBUG")
def greet():
    print("안녕하세요")

또는

@repeat(3)
def hello():
    print("Hello")

이처럼 @ 뒤의 데코레이터에 괄호를 붙이고 값을 전달하는 형태를 인자를 가지는 데코레이터라고 합니다.

일반 데코레이터와 인자를 가지는 데코레이터

먼저 두 형태를 비교해보겠습니다.

일반적인 데코레이터는 다음과 같습니다.

@decorator
def greet():
    print("안녕하세요")

이 경우 decorator가 바로 greet 함수를 전달받습니다.

개념적으로 다음과 같습니다.

greet = decorator(greet)

반면 인자를 가지는 데코레이터는 다음과 같습니다.

@decorator("DEBUG")
def greet():
    print("안녕하세요")

이 경우에는 바로 greet가 전달되는 것이 아닙니다.

먼저

decorator("DEBUG")

가 실행되고, 그 결과로 실제 데코레이터 함수가 반환되어야 합니다.

그 다음 반환된 데코레이터가 greet를 전달받습니다.

즉 개념적으로 다음과 같은 구조입니다.

@decorator("DEBUG")
        ↓
decorator("DEBUG") 실행
        ↓
실제 데코레이터 반환
        ↓
greet 함수 전달
        ↓
greet = 실제 데코레이터(greet)

이 구조가 인자를 가지는 데코레이터를 이해하는 핵심입니다.

가장 간단한 인자 데코레이터 만들기

먼저 message라는 값을 받아서 함수 실행 전에 출력하는 데코레이터를 만들어보겠습니다.

def log(message):

    def decorator(func):

        def wrapper(*args, **kwargs):
            print(message)
            return func(*args, **kwargs)

        return wrapper

    return decorator

사용 방법은 다음과 같습니다.

@log("함수를 실행합니다.")
def greet():
    print("안녕하세요")

이제 greet()를 호출하면,

greet()

결과:

함수를 실행합니다.
안녕하세요

여기서 log()가 데코레이터에 전달할 값을 받는 역할을 합니다.

왜 함수가 세 겹이나 필요한가?

처음 보면 다음 구조가 복잡하게 느껴질 수 있습니다.

def log(message):

    def decorator(func):

        def wrapper(*args, **kwargs):
            print(message)
            return func(*args, **kwargs)

        return wrapper

    return decorator

각 함수의 역할을 구분하면 이해하기 쉽습니다.

첫 번째 함수: 데코레이터의 인자를 받는다

def log(message):

여기서 message가 데코레이터에 전달되는 값입니다.

@log("DEBUG")

그러면 "DEBUG"가 message로 전달됩니다.

두 번째 함수: 실제 함수를 받는다

def decorator(func):

여기서는 실제로 꾸밀 함수가 전달됩니다.

예를 들어,

@log("DEBUG")
def greet():
    ...

에서 greet가 func로 전달됩니다.

세 번째 함수: 실제 함수의 실행을 감싼다

def wrapper(*args, **kwargs):

여기서는 실제 greet()가 호출될 때 실행됩니다.

그리고 내부에서 원래 함수를 호출합니다.

return func(*args, **kwargs)

정리하면 다음과 같습니다.

log(message)
    ↓
데코레이터에 전달할 값 저장
    ↓
decorator(func)
    ↓
꾸밀 함수 전달
    ↓
wrapper(*args, **kwargs)
    ↓
실제 함수 실행

@ 문법을 풀어서 생각하기

다음 코드를 보겠습니다.

@log("DEBUG")
def greet():
    print("안녕하세요")

이 코드는 한 번에 이해하려고 하면 복잡합니다.

개념적으로 풀어서 보면 다음과 같습니다.

먼저,

decorator = log("DEBUG")

가 실행됩니다.

여기서 log("DEBUG")는 실제 데코레이터 함수인 decorator를 반환합니다.

그다음,

greet = decorator(greet)

가 실행됩니다.

따라서 전체적으로는 다음과 같은 구조라고 생각하면 됩니다.

log("DEBUG")
     ↓
decorator 함수 반환
     ↓
decorator(greet)
     ↓
wrapper 함수 반환
     ↓
greet가 wrapper를 가리킴

이 흐름을 이해하면 인자를 가지는 데코레이터가 훨씬 쉬워집니다.

여러 개의 인자를 받을 수도 있다

데코레이터는 하나의 인자만 받을 필요가 없습니다.

여러 개의 값을 전달할 수도 있습니다.

def log(level, message):

    def decorator(func):

        def wrapper(*args, **kwargs):
            print(f"[{level}] {message}")
            return func(*args, **kwargs)

        return wrapper

    return decorator

사용할 때는 다음과 같이 작성합니다.

@log("INFO", "함수를 실행합니다.")
def greet():
    print("안녕하세요")

실행하면,

greet()

결과:

[INFO] 함수를 실행합니다.
안녕하세요

데코레이터를 적용하는 시점에 필요한 값을 전달할 수 있습니다.

데코레이터의 인자와 함수의 인자는 다르다

여기서 중요한 점이 하나 있습니다.

다음 코드에는 서로 다른 두 종류의 인자가 존재합니다.

@log("INFO")
def greet(name):
    print(f"{name}님 안녕하세요")

"INFO"는 데코레이터의 인자입니다.

log("INFO")

반면 "Python"은 greet 함수의 인자입니다.

greet("Python")

두 값은 전달되는 시점과 대상이 다릅니다.

데코레이터 적용 시점
    ↓
log("INFO")
    ↓
데코레이터 설정

함수 호출 시점
    ↓
greet("Python")
    ↓
원래 함수 실행

이 차이를 구분하는 것이 중요합니다.

함수의 인자도 그대로 전달할 수 있다

데코레이터 자체에 인자를 전달하면서 원래 함수의 인자도 처리할 수 있습니다.

def log(message):

    def decorator(func):

        def wrapper(*args, **kwargs):
            print(message)

            result = func(*args, **kwargs)

            return result

        return wrapper

    return decorator

그리고 다음과 같이 사용할 수 있습니다.

@log("사용자 정보 출력")
def introduce(name, age):
    return f"{name}은 {age}살입니다."

호출합니다.

result = introduce("Python", 10)

print(result)

결과:

사용자 정보 출력
Python은 10살입니다.

여기서 "사용자 정보 출력"은 데코레이터의 인자이고, "Python"과 10은 introduce() 함수의 인자입니다.

기본값을 사용할 수도 있다

일반 함수와 마찬가지로 데코레이터의 인자에도 기본값을 지정할 수 있습니다.

def log(level="INFO"):

    def decorator(func):

        def wrapper(*args, **kwargs):
            print(f"[{level}] 함수 실행")
            return func(*args, **kwargs)

        return wrapper

    return decorator

이제 다음과 같이 사용할 수 있습니다.

@log()
def greet():
    print("안녕하세요")

결과:

[INFO] 함수 실행
안녕하세요

원하는 경우 값을 직접 전달할 수도 있습니다.

@log("DEBUG")
def test():
    print("테스트")

결과:

[DEBUG] 함수 실행
테스트

인자를 가지는 데코레이터를 어디에 사용할까?

인자를 가지는 데코레이터는 데코레이터의 동작을 상황에 따라 변경하고 싶을 때 유용합니다.

예를 들어 로그 레벨을 지정할 수 있습니다.

@log("INFO")
def save_data():
    ...

다른 함수에는 다른 레벨을 지정할 수도 있습니다.

@log("DEBUG")
def check_data():
    ...

또는 반복 횟수를 지정하는 데코레이터를 만들 수도 있습니다.

@repeat(3)
def hello():
    print("Hello")

이 경우 3이라는 값을 이용해 함수 실행 횟수를 결정할 수 있습니다.

repeat 데코레이터 만들어보기

간단한 반복 데코레이터를 만들어보겠습니다.

def repeat(count):

    def decorator(func):

        def wrapper(*args, **kwargs):

            for _ in range(count):
                func(*args, **kwargs)

        return wrapper

    return decorator

사용할 때는 다음과 같습니다.

@repeat(3)
def hello():
    print("Hello")

실행합니다.

hello()

결과:

Hello
Hello
Hello

repeat(3)에서 전달한 3을 이용해 wrapper()가 함수를 세 번 실행합니다.

이처럼 데코레이터의 인자를 이용하면 같은 데코레이터라도 설정값에 따라 다른 방식으로 동작하도록 만들 수 있습니다.

반환값도 유지해야 한다

앞에서 배운 것처럼 원래 함수가 반환하는 값을 사용하는 경우에는 wrapper()에서도 반환해야 합니다.

예를 들어 반복 데코레이터가 함수의 반환값을 필요로 한다면 단순히 func()만 실행해서는 안 됩니다.

result = func(*args, **kwargs)
return result

기본적인 인자 데코레이터의 형태는 다음처럼 기억해두면 좋습니다.

def decorator_with_args(value):

    def decorator(func):

        def wrapper(*args, **kwargs):
            result = func(*args, **kwargs)
            return result

        return wrapper

    return decorator

여기서 각각의 역할은 명확합니다.

decorator_with_args(value)
→ 데코레이터 설정값

decorator(func)
→ 꾸밀 함수

wrapper(*args, **kwargs)
→ 실제 함수 호출 처리

일반 데코레이터와 비교

두 구조를 비교하면 차이가 더 분명합니다.

일반 데코레이터:

def decorator(func):

    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)

    return wrapper

사용:

@decorator
def greet():
    ...

인자를 가지는 데코레이터:

def decorator_with_args(value):

    def decorator(func):

        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)

        return wrapper

    return decorator

사용:

@decorator_with_args("값")
def greet():
    ...

즉 인자를 가지는 데코레이터는 함수를 한 단계 더 감싸는 구조가 필요합니다.

마무리

인자를 가지는 데코레이터는 처음에는 함수가 세 겹으로 중첩되어 있어 복잡해 보일 수 있습니다.

하지만 각각의 역할을 나누어 생각하면 어렵지 않습니다.

1단계
데코레이터 설정값
        ↓
2단계
실제로 꾸밀 함수
        ↓
3단계
함수 실행과 인자 처리

기본 구조는 다음과 같습니다.

def decorator_with_args(value):

    def decorator(func):

        def wrapper(*args, **kwargs):
            result = func(*args, **kwargs)
            return result

        return wrapper

    return decorator_with_args

실제 사용할 때는 다음과 같은 형태가 됩니다.

@decorator_with_args("설정값")
def function():
    ...

핵심은 @decorator와 @decorator()가 동작하는 방식이 다르다는 점입니다.

@decorator는 함수를 바로 데코레이터에 전달하지만, @decorator() 형태는 먼저 데코레이터의 설정값을 전달하고 그 결과로 실제 데코레이터를 얻은 다음 함수를 전달합니다.

다음 글에서는 지금까지 배운 데코레이터를 실제 상황에 적용해보겠습니다. 실행 시간 측정, 로그 기록, 권한 확인, 함수 호출 제어 등 데코레이터를 활용하는 방법을 중심으로 살펴보겠습니다.

jiji

AI, Python, 생활정보와 정부 정책 정보를 쉽게 설명하는 정보 블로그입니다.

Ad Placeholder
Scroll to Top