오늘은 Python의 데코레이터를 사용하는 방법에 대해 정리해보자.
@decorator_ # <-- 요런거
def function():
print "what is decorator?"Github등 오픈 소스 커뮤니티에 공유된 코드들을 읽다보면, 위와 같이 ‘@’ 기호로 표시된 코드들을 자주 볼 수 있다.
오늘 알아보려하는 데코레이터는 보통 이 ‘@‘기호로 정의된 함수 또는 클래스를 의미한다.
데코레이터를 간단히 설명하면,
“다른 함수를 감싸는 함수"로, 감싸진 함수에 추가적인 기능을 제공하거나, 감싸진 함수의 동작을 변경하는데 사용되는 기능이다.
But, 위 설명만 가지고는 사실 잘 이해가 되지않는다.
우선은 **‘다른 함수를 감싸는 함수’**라는 부분만 기억하고 넘어가자.
#
데코레이터…? 어떻게 사용하는거지?#
데코레이터는 함수 형태로 사용하는 방법과 클래스로 사용하는 방법이 있다.
먼저, 함수로 데코레이터를 사용하는 방법에 대해 먼저 살펴보자.
1. 함수형태로 사용하는 방법#
위에서 데코레이터는 함수를 감싸는 함수로써, 감싸진 함수에 추가적인 기능을 제공하거나, 동작을 변경하는데 사용된다고했다.
그 예시로 아래 코드를 살펴보자.
import time
def timer_decorator(func):
def wrappers(*arg, **kwargs):
start_time = time.time()
result = func(*arg, **kwargs)
end_time = time.time()
print(f"{func.__name__} 실행 시간: {end_time - start_time}초")
return result
return wrappers
@timer_decorator
def example_function():
for _ in range(1000000):
pass
example_function()
"""
[OUTPUT]
example_function 실행 시간: 0.04586601257324219초
"""위 코드는 간단한 roof 함수를 데코레이터로 wrapping한 예시 코드인데, 위 코드에서 데코레이터가 동작하는 순서를 아래에 정리해보자.
(1) 가장 먼저, ’time_decorator’이라는 데코레이터 함수를 생성한다.
함수의 내용은, 인자로 받은 ‘func’함수를 실행하고, 함수 실행 이전과 이후에 시간을 체크하여, 함수의 실행시간을 측정하여 출력하는
timer함수이다.
(2) 그 다음, 만들어진 데코레이터 함수를 통해 ‘감쌀 함수’로 ’example_function’이라는 함수를 정의했다.
해당 함수는 100만번의 루프를 도는 간단한 함수이다.
(3) 그리고 해당 함수를 ‘@‘를 통해 ‘@timer_decorator’함수로 대상 함수를 감싸주면 데코레이터 적용이 완료된다.
이렇게 함수를 구성하게 되면, 아래에서 ’example_function()‘을 통해 감싸진 함수를 호출할때,
해당 함수는 ‘@timer_decorator’이라는 데코레이션 함수로 wrapping된 상태이기 때문에,
데코레이터 함수 ’timer_decorator’ 의 인자로 example_function함수가 들어가게 되고, 데코레이터 함수가 실행된다.
그리고, 데코레이터 함수내에서는, 인자로 받은 ’example_fuanction’함수가 실행되고, 해당 함수의 실행시간이 측정되어
시간이 출력된다.
즉, 함수 실행은 ’example_function()‘로 호출했지만,
해당 함수가 decorator로 wrapping되어있었기 때문에,
wrapping된 데코레이터 함수 내부에서 인자로 받은 ’example_function’함수를 실행한 결과값을 반환하게 된 것이다.

이해가 되는가..?
여기서 데코레이터 함수를 구성할때, 주의해야할 점이 있다.
import datetime
def time_check(func):
print(datetime.datetime.now())
func()
print(datetime.datetime.now())
@time_check
def output_func():
print('이거 위아래로 시간을 찍어라')
output_func()
"""
[OUTPUT]
2024-02-07 11:57:21.361220
이거 위아래로 시간을 찍어라
2024-02-07 11:57:21.362256
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
/var/folders/2g/2b3fqv816cb5kxrlc3v553280000gn/T/ipykernel_10484/1157037113.py in <module>
10 print('이거 위아래로 시간을 찍어라')
11
---> 12 output_func()
TypeError: 'NoneType' object is not callable
"""위 데코레이터 함수는 언뜻 봤을때는 큰 문제가 없어보이지만, 오류가 발생한다.
오류와 더불어서 동작에도 문제가 있는데, ‘output_func()‘와 같이 output_func함수가 호출되는 시점에서,
데코레이터를 포함한 output_func 함수가 실행되어야 하지만,
위 출력된 결과값을 보면,
‘output_func()‘가 호출되기 전에 이미,
2024-02-07 11:57:21.361220
이거 위아래로 시간을 찍어라
2024-02-07 11:57:21.362256함수의 결과값이 출력되었고, 이후에 output_func() 호출과정에서 오류가 발생한 것을 확인할 수 있다.
이 오류와 관련해서 우리가 데코레이터를 사용함에 있어, 기억해야할 것은
데코레이터함수 내부에서 wrapping된 함수를 불러와 실행할때는 데코레이터 내부에 또다른 함수(wrapper), 즉 위에서 설명한 올바른 코드의 예시에서 ‘wrappers’함수와 같은 데코레이터 함수 내에, wrapper함수를 정의해서, 인자로 받은 외부 함수를 감싸야 한다는 것이다.
반드시, 데코레이터 함수 내부의 wrapper함수로 외부함수를 감싸야지만, ‘output_func’함수가
‘호출되는 시점’에 실행되는, 올바른 동작 형태를 가지게 된다.
이 부분은 데코레이터 함수를 사용하는 규칙이므로 사용방식을 이해하고 넘어가면 된다.
동작방식은 ok… 그래서, 데코레이터를 왜 사용하는거지?#
def example_function1():
start_time = time.time()
for _ in range(5000000):
pass
end_time = time.time()
print(f"example_function1 실행 시간: {end_time - start_time}초")
def example_function2():
start_time = time.time()
for _ in range(5000000):
pass
end_time = time.time()
print(f"example_function2 실행 시간: {end_time - start_time}초")
def example_function3():
start_time = time.time()
for _ in range(5000000):
pass
end_time = time.time()
print(f"example_function3 실행 시간: {end_time - start_time}초")
example_function1()
example_function2()
example_function3()
"""
example_function1 실행 시간: 0.10071301460266113초
example_function2 실행 시간: 0.10005807876586914초
example_function3 실행 시간: 0.10119986534118652초
"""만약 데코레이터를 사용하지 않고 위의 example_function1~3함수 각각 실행시간을 체크하려고 한다면,
아마도 위의 코드와 같이 각 함수별로 timer 코드를 중복으로 넣어줘야할것이다.
하지만, 데코레이터를 사용하면 각 함수에 중복으로 활용되는 코드를 반복적으로 입력할 필요없이,
별도로 정의된 데코레이터 함수를 통해 간결하고 가독성 좋은 코드를 작성할 수 있다.
아래 코드에서 데코레이터 활용시의 장점을 확인할 수 있다.
def timer_decorator(func):
def wrapper(*arg, **kwargs):
start_time = time.time()
result = func(*arg, **kwargs)
end_time = time.time()
print(f"{func.__name__} 실행 시간: {end_time - start_time}초")
return result
return wrapper
@timer_decorator
def example_function1():
for _ in range(1000000):
pass
@timer_decorator
def example_function2():
for _ in range(10000000):
pass
@timer_decorator
def example_function3():
for _ in range(5000000):
pass
example_function1()
example_function2()
example_function3()
"""
[OUTPUT]
example_function1 실행 시간: 0.02248668670654297초
example_function2 실행 시간: 0.2073652744293213초
example_function3 실행 시간: 0.10090112686157227초
"""2. 클래스 형태로 사용하는 방법#
함수로 데코레이터를 사용하는 방법을 이해했다면, 클래스로 사용하는 방법은 금방 이해할 수 있다.
클래스 데코레이터는,
별도의 데코레이터 클래스를 만든다.
데코레이터 클래스 내부의 __call__ 매서드 내부에, 데코레이터를 통해 실행하고자하는 코드를 넣어 정의한다.
나머지는 다 똑같다!
아래 코드에서 확인해보자.
class Timer_Decorator:
def __init__(self, func):
self.func = func
def __call__(self, *arg, **kwargs):
start_time = time.time()
self.func(*arg, **kwargs)
end_time = time.time()
print(f'{self.func.__name__} 실행시간: {end_time-start_time}초')
class MainClass:
@Timer_Decorator
def example_function1():
for _ in range(1000000):
pass
@Timer_Decorator
def example_function2():
for _ in range(10000000):
pass
@Timer_Decorator
def example_function3():
for _ in range(5000000):
pass
this_class = MainClass()
this_class.example_function1()
this_class.example_function2()
this_class.example_function3()
"""
[OUTPUT]
example_function1 실행시간: 0.029875993728637695초
example_function2 실행시간: 0.19496917724609375초
example_function3 실행시간: 0.09695100784301758초
"""설명한것과 같이 데코레이터 클래스의 __call__매서드 내부에, 데코레이터를 실행할 때 호출할 코드내용을 담는다.
데코레이터는 클래스명으로 정의하면 된다.
위 코드와 같이, 외부 클래스 내부에 속한 내장함수들의 위에 데코레이터(클래스)를 붙여주고,
각각의 내장함수를 호출하면, wrapping된 데코레이터의 내용을 타고 출력물이 생성되는것을 확인할 수 있다.
위에서는 클래스 내부의 내장함수에 데코레이터를 붙이는 예시로 들었지만,
클래스로 생성한 데코레이터를 일반 함수에 바로 붙여도, 아래와 같이 동일하게 잘 동작한다.
class Timer_Decorator:
def __init__(self, func):
self.func = func
def __call__(self, *arg, **kwargs):
start_time = time.time()
self.func(*arg, **kwargs)
end_time = time.time()
print(f'{self.func.__name__} 실행시간: {end_time-start_time}초')
@Timer_Decorator
def example_function1():
for _ in range(1000000):
pass
@Timer_Decorator
def example_function2():
for _ in range(10000000):
pass
@Timer_Decorator
def example_function3():
for _ in range(5000000):
pass
this_class = MainClass()
example_function1()
example_function2()
example_function3()
"""
[OUTPUT]
example_function1 실행시간: 0.03767681121826172초
example_function2 실행시간: 0.20968103408813477초
example_function3 실행시간: 0.09826517105102539초
"""마치며#
오늘은 파이썬 데코레이터에 대해서 알아보았다.
데코레이터는 잘만 사용하면 반복적으로 작성해야하는 코드들을 줄여주기때문에, 코드의 간결성을 높일 수 있고,
기능별로 정의하여 함수 및 클래스에 이식하여 사용할 수 있기 때문에
코드의 가독성을 높이고, 객체지향적 코드를 구현하는데도 좋은 기능이라고 생각된다.
다만, 데코레이터에는 별도의 추가 인자를 받아 처리하는 방법 등 생각보다 매우 다양한 방식의 활용 케이스들이 있다.
따라서, 위 기본적인 내용을 바탕으로 좀 더 여러 케이스들을 접하고 활용해보는 공부가 필요할 것 같다.
Reference#
[python decorator (데코레이터) 어렵지 않아요
Python decorator (데코레이터) Python 으로 작성된 Opensource 의 코드들을 보다 보면, 아래와 같이 @ 로 시작하는 구문 들을 볼 수 있다. @decorator_def function(): print “what is decorator?” decorator를 한마디로 얘기
bluese05.tistory.com](https://bluese05.tistory.com/30)
