소개
본 실습에서는 Python 데코레이터 (Decorator) 에 대해 학습합니다. 데코레이터는 함수나 클래스의 기능을 영구적으로 수정하지 않으면서 간결하고 재사용 가능한 방식으로 기능을 확장할 수 있게 해주는 강력한 기능입니다.
간단한 데코레이터를 만드는 것부터 시작하여, 함수 매개변수 (parameter) 를 처리하는 방법, functools.wraps를 사용하여 모범 사례를 적용하는 방법, 자체 인수를 받는 데코레이터를 만드는 방법, 그리고 마지막으로 상태 (state) 를 관리하기 위해 클래스를 사용하여 데코레이터를 구현하는 방법을 배웁니다.
최소한의 데코레이터
이 단계에서는 Python 데코레이터의 기본적인 구조를 배웁니다. 가장 간단한 형태의 데코레이터는 다른 함수를 인수로 받아, 추가적인 기능을 더한 다음, 또 다른 함수를 반환하는 함수입니다.
참고: WebIDE(VS Code 와 유사) 사용에 익숙하지 않다면, 이 랩 (lab)에서 먼저 학습하십시오.
먼저, 왼쪽 파일 탐색기에서 step1.py 파일을 엽니다.
이제 다음 코드를 step1.py 파일에 복사합니다. 이 코드는 간단한 데코레이터를 정의하고 함수에 적용합니다.
## Decorator: a function that takes another function and returns a wrapper
def your_decorator(func):
def wrapper():
## Do stuff before func...
print("Before func!")
func() ## Call the original function
## Do stuff after func...
print("After func!")
return wrapper ## Return the wrapper function
## @your_decorator is syntactic sugar for: foo = your_decorator(foo)
@your_decorator
def foo():
print("Hello World!")
foo() ## Calls wrapper, which calls foo with extra behavior
코드를 분석해 보겠습니다.
your_decorator(func): 이것이 데코레이터 함수입니다. 장식할 함수인func라는 하나의 인수를 받습니다.wrapper(): 이것은 원래 함수를 "감싸는 (wrap)" 내부 함수입니다. 추가하려는 새로운 동작을 포함합니다. 여기서는 원래 함수를 호출하기 전과 후에 메시지를 출력합니다.@your_decorator: 이것은 데코레이터를 적용하기 위한 문법적 설탕 (syntactic sugar) 입니다. 함수 정의 후foo = your_decorator(foo)라고 작성하는 것과 동일합니다.
이제 터미널에서 스크립트를 실행하여 데코레이터가 작동하는 것을 확인해 보세요.
python3 step1.py
원래 foo 함수 주위에 동작을 추가하는 wrapper 함수가 실행되었음을 보여주는 다음 출력을 보게 될 것입니다.
Before func!
Hello World!
After func!
매개변수가 있는 함수를 위한 데코레이터
이 단계에서는 인수를 받는 함수와 함께 작동하도록 데코레이터를 확장합니다. 실제 함수는 종종 매개변수를 가지며, 우리의 데코레이터는 이를 올바르게 처리할 수 있어야 합니다.
이를 위해 wrapper 함수의 시그니처에서 *args와 **kwargs를 사용할 수 있습니다. 이를 통해 래퍼는 임의의 수의 위치 인수 (positional arguments) 와 키워드 인수 (keyword arguments) 를 받아 원래 함수로 전달할 수 있습니다.
step2.py 파일을 열고 다음 코드를 추가합니다.
## Decorator that works with functions that have parameters
def your_decorator(func):
def wrapper(*args,**kwargs): ## Accept any arguments
## Do stuff before func...
print("Before func!")
func(*args,**kwargs) ## Pass arguments to original function
## Do stuff after func...
print("After func!")
return wrapper
@your_decorator
def foo(bar):
print("My name is " + bar)
foo("Jack") ## Arguments are passed through wrapper
이 코드에서 wrapper 함수는 *args와 **kwargs로 정의됩니다. foo("Jack")이 호출되면, 인수 "Jack"은 wrapper의 *args에 캡처된 후 func(*args, **kwargs)가 실행될 때 원래의 func로 전달됩니다.
스크립트를 실행하여 결과를 확인합니다.
python3 step2.py
출력 결과는 인수가 데코레이터를 통해 원래 함수로 성공적으로 전달되었음을 보여줍니다.
Before func!
My name is Jack
After func!
기본 데코레이터 템플릿
이 단계에서는 강력한 데코레이터를 생성하기 위한 모범 사례 템플릿을 알아봅니다. 이전 데코레이터들도 작동했지만, 원본 함수의 메타데이터 (예: 이름 __name__ 및 독스트링 __doc__) 를 잃어버리는 부작용이 있었습니다.
이 문제를 해결하기 위해 Python 의 functools 모듈은 wraps라는 헬퍼 데코레이터를 제공합니다. 또한, 좋은 데코레이터는 반환 값이 있는 함수도 처리할 수 있어야 합니다.
이 템플릿은 대부분의 데코레이터 사용 사례에 유용합니다. 매개변수가 있거나 없는 함수, 반환 값이 있거나 없는 함수 모두에 유효합니다.
step3.py 파일을 열고 다음 코드를 추가합니다.
import functools
## Best practice decorator template: preserves function metadata and return value
def your_decorator(func):
@functools.wraps(func) ## Preserves function name, docstring, etc.
def wrapper(*args,**kwargs):
print("Before function execution...")
result = func(*args,**kwargs) ## Call function and capture return value
print("After function execution...")
return result ## Return the original function's return value
return wrapper
@your_decorator
def add(a, b):
"""This function adds two numbers."""
return a + b
sum_result = add(5, 3)
print(f"Function name: {add.__name__}")
print(f"Docstring: {add.__doc__}")
print(f"Result: {sum_result}")
이 템플릿의 주요 개선 사항:
import functools: 필요한 모듈을 가져옵니다.@functools.wraps(func):wrapper함수에 적용되는 데코레이터입니다. 원본func의 메타데이터 (__name__및__doc__등) 를wrapper함수로 복사합니다.result = func(*args, **kwargs): 원본 함수의 반환 값이result변수에 캡처됩니다.return result: 래퍼 함수는 캡처된 결과를 반환하므로 장식된 함수가 예상대로 작동합니다.
스크립트를 실행합니다.
python3 step3.py
출력 결과는 함수의 이름과 독스트링이 보존되었으며 반환 값이 올바르게 처리되었음을 보여줍니다.
Before function execution...
After function execution...
Function name: add
Docstring: This function adds two numbers.
Result: 8
매개변수가 있는 데코레이터
이 단계에서는 자체 매개변수로 사용자 정의할 수 있는 데코레이터를 만드는 방법을 배웁니다. 이를 통해 @your_decorator(arg='value')와 같이 보다 유연하고 구성 가능한 데코레이터 사용이 가능해집니다.
이를 달성하려면 인수를 받아 데코레이터를 반환하는 "데코레이터 팩토리 (decorator factory)"를 생성해야 합니다. 이는 구조에 추가적인 중첩 계층을 추가합니다.
prefix 문자열을 받아 함수 호출 전후에 이를 출력하는 데코레이터를 만들어 보겠습니다. step4.py를 열고 다음 코드를 추가합니다.
import functools
## Decorator factory: returns a decorator based on parameters
def prefix_decorator(prefix):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
## Use the 'prefix' argument from the factory
print(f"{prefix} Before function call")
result = func(*args, **kwargs)
print(f"{prefix} After function call")
return result
return wrapper
return decorator ## Return the actual decorator function
## Using the decorator with a parameter
@prefix_decorator(prefix="LOG:")
def say_hello(name):
print(f"Hello, {name}!")
say_hello("Alice")
작동 방식은 다음과 같습니다.
- 먼저
prefix_decorator(prefix="LOG:")가 호출됩니다. 이것이 팩토리 함수입니다. - 이 함수는
decorator함수를 반환하며, 이decorator함수는 클로저 (closure) 를 통해prefix변수 ("LOG:") 에 접근할 수 있게 됩니다. - 이렇게 반환된
decorator가say_hello에 적용되어 이를 장식합니다.
매개변수가 어떻게 사용되는지 확인하기 위해 스크립트를 실행합니다.
python3 step4.py
출력 결과는 데코레이터에 인수로 전달된 "LOG:" 접두사가 사용되었음을 보여줍니다.
LOG: Before function call
Hello, Alice!
LOG: After function call
클래스 기반 데코레이터
이 단계에서는 클래스를 사용하여 데코레이터를 구현하는 방법을 살펴봅니다. 이 접근 방식은 함수 호출 간에 상태 (state) 를 유지해야 할 때 특히 유용합니다. 클래스 내 메서드 장식과 데코레이터 자체를 클래스로 정의하는 두 가지 패턴을 다룰 것입니다.
시작하려면 step5.py 파일을 엽니다.
패턴 1: 클래스 메서드 장식
클래스 내부에 데코레이터를 메서드로 정의하고 동일한 클래스의 다른 메서드에 적용할 수 있습니다. 이는 클래스 컨텍스트에 묶인 특정 동작을 생성할 때 유용합니다.
step5.py에 다음 코드를 추가합니다. 이 예제는 클래스 메서드에 대한 예외 처리를 위한 데코레이터를 정의합니다.
## Pattern 1: Decorating a method within a class
class DecorateMyMethod:
## Static method decorator for methods with only 'self' parameter
def decorator_for_class_method_with_no_args(method):
def wrapper_for_class_method(self): ## Only takes self
try:
return method(self) ## Call original method
except Exception as e:
print(f"\nWARNING: An exception occurred: {e}\n")
return wrapper_for_class_method
def __init__(self,succeed:bool):
self.succeed = succeed
@decorator_for_class_method_with_no_args
def class_action(self):
if self.succeed:
print("You succeeded by choice.")
else:
raise Exception("Epic fail of your own creation.")
print("--- Testing Pattern 1 ---")
test_succeed = DecorateMyMethod(True)
test_succeed.class_action()
test_fail = DecorateMyMethod(False)
test_fail.class_action()
print("-" * 25)
패턴 2: 클래스 형태의 데코레이터
더 일반적이고 강력한 패턴은 데코레이터 자체를 클래스로 정의하는 것입니다. 이는 데코레이터가 상태를 유지해야 할 때 이상적입니다. __init__() 및 __call__() 메서드를 구현하면 클래스 인스턴스가 함수처럼 동작하도록 만들 수 있습니다.
함수가 호출된 횟수를 세는 데코레이터를 만들어 보겠습니다. step5.py 끝에 다음 코드를 추가합니다.
## Pattern 2: Class-based decorator for maintaining state
class CountCallNumber:
def __init__(self, func):
self.func = func ## Store the function to decorate
self.call_number = 0 ## Initialize call counter
def __call__(self, *args, **kwargs): ## Makes instance callable
self.call_number += 1 ## Increment counter
print("This is execution number " + str(self.call_number))
return self.func(*args, **kwargs) ## Call original function
@CountCallNumber
def say_hi(name):
print("Hi! My name is " + name)
print("\n--- Testing Pattern 2 ---")
say_hi("Jack")
say_hi("James")
__init__(self, func): 이것은 생성자입니다. 데코레이터가 적용될 때 (@CountCallNumber) 호출됩니다. 장식될 함수 (say_hi) 를 받아 저장합니다.__call__(self, *args, **kwargs): 이 메서드는 클래스 인스턴스를 호출 가능하게 만듭니다. 장식된 함수 (say_hi("Jack")) 를 호출할 때, 실제로는 이__call__메서드를 호출하는 것입니다. 이 메서드는 카운터를 증가시킨 다음 원래 저장된 함수를 호출합니다.
이제 전체 스크립트를 실행합니다.
python3 step5.py
두 패턴에 대한 출력을 볼 수 있습니다. 두 번째 부분에서 호출 카운터가 증가하는 것을 확인하세요.
--- Testing Pattern 1 ---
You succeeded by choice.
WARNING: An exception occurred: Epic fail of your own creation.
-------------------------
--- Testing Pattern 2 ---
This is execution number 1
Hi! My name is Jack
This is execution number 2
Hi! My name is James
요약
실습을 완료하신 것을 축하드립니다! Python 데코레이터와 그 다양한 형태에 대해 탄탄한 이해를 얻으셨습니다.
본 실습에서 다음 사항들을 배웠습니다.
- 기본 데코레이터를 생성하고
@구문을 이해하는 방법. *args와**kwargs를 사용하여 모든 수의 인수를 받는 함수와 함께 작동하는 데코레이터를 설계하는 방법.- 함수 메타데이터를 보존하고 반환 값을 처리하기 위해
functools.wraps를 사용하는 모범 사례 템플릿. - 데코레이터가 매개변수로 사용자 정의될 수 있도록 하는 데코레이터 팩토리 (decorator factories) 를 구축하는 방법.
- 함수 호출 간에 상태 (state) 를 유지하기 위해 데코레이터를 클래스로 구현하는 방법 (예: 호출 횟수 카운터).
데코레이터는 깔끔하고 재사용 가능하며 확장 가능한 Python 코드를 작성하는 데 핵심적인 기능입니다. 이제 이러한 패턴을 자신의 프로젝트를 개선하는 데 적용할 수 있습니다.



