앞에서는 데코레이터에 함수의 인자와 반환값을 전달하는 방법을 알아봤습니다.
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() 형태는 먼저 데코레이터의 설정값을 전달하고 그 결과로 실제 데코레이터를 얻은 다음 함수를 전달합니다.
다음 글에서는 지금까지 배운 데코레이터를 실제 상황에 적용해보겠습니다. 실행 시간 측정, 로그 기록, 권한 확인, 함수 호출 제어 등 데코레이터를 활용하는 방법을 중심으로 살펴보겠습니다.