파이썬은 요즘 배울 수 있는 간단한 언어지만 올바르게 이해하려면 주의가 필요한 영역이 있습니다. 데코레이터(decorator)도 그 중 하나입니다. 데코레이션을 이해하기 위한 단계별 접근 방식을 소개합니다.
※ 이 글은 아래 기사 내용을 토대로 작성되었습니다만, 필자의 개인 의견이나 추가 자료들이 다수 포함되어 있습니다.
- 원문: Simplifying Decorators in python | A step-by-step approach
- URL: https://akhiltvsn.medium.com/simplifying-decorators-in-python-a-step-by-step-approach-618bb9f5dd67
단계별 접근
이런 경우를 상상해 보세요. 모든 숫자를 실수 k까지 가능한 한 빨리 더해야 하는 프로젝트를 진행하고 있습니다. 이 작업을 수행하는 방법에는 여러 가지가 있습니다. for 루프를 통해 모든 숫자를 계속 더할 수 있습니다. 처음 n개의 자연수의 합에 대한 표현식을 직접 사용할 수 있습니다. 아니면 다른 방법을 생각해낼 수도 있습니다.
def add_directly(k):
result = 0
for i in range(k):
result+=i+1
return result
def use_expression(k):
return k*(k+1)/2
이제 이 두 가지 기능으로 작업을 완료할 수 있습니다. 하지만 기억하시겠지만 가장 빠른 방법이 필요합니다. 이를 위해서는 각 함수에 걸리는 시간을 구해야 합니다. 이 함수를 두 번 직접 실행하고 각 함수에 걸리는 시간을 인쇄할 수 있습니다.
start = time.time()
print(add_directly(30000))
end = time.time()
print(f’Time taken by adding directly: {end-start} \n’)
start = time.time()
print(use_expression(30000))
end = time.time()
print(f’Time taken by the expression: {end-start} \n’)
이것은 일반적인 규칙인 DRY(반복하지 않기)를 위반하는 것입니다. 그렇다면 어떻게 해야 할까요? 다른 함수가 걸린 시간을 평가하는 것을 목적으로 하는 새로운 함수를 작성할 수 있습니다.
def evaluate_time(func):
"""
Evaluates the time taken for a function to run.
Args:
func (function): any generic function
"""
k = 300000
start = time.time()
print(f’Function Output: {func(k)}
end = time.time()
return end-start
이제 각 함수에 걸린 시간을 알고 싶을 때 이전에 정의한 evaluate_time 함수를 사용하면 됩니다. 이제 데코레이터의 최종 의미에 가까워졌지만 아직 완성되지는 않았습니다.
함수 래핑의 의도를 명확히 알았으니 이제 정리할 필요가 있습니다. 래핑된 함수를 실행하려면 평가_시간(add_directly)과 같이 실행해야 합니다. 수십 개, 때로는 수백 개의 함수에 대해 래핑을 테스트하려면 매번 이 래퍼(func)를 반복해야 합니다. 이는 다시 한 번 DRY 규칙과 모순됩니다. 그럼 어떻게 해야 할까요?
다시 한 번 다른 함수를 감싸는 함수를 정의합니다. 이 함수를 '데코레이터'라고 부릅니다
def time_decorator_function(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f'Time taken {func.__name__}: {end-start}')
return result
return wrapper
@time_decorator_function
def add_directly(k):
result = 0
for i in range(k):
result+=i+1
return result
@time_decorator_function
def use_expression(k):
return k*(k+1)/2
여기서 무슨 일이 일어났을까요?
기본적으로 추가 코드 줄로 함수를 래핑하여 add_directly 함수를 실행할 때마다 실행하는 데 걸린 시간을 출력하도록 했습니다. 즉, add_directly를 이전 버전의 add_directly를 포함하는 래퍼로 변경했습니다. 이는 x=x+1을 설정하는 방법, 즉 x에 저장된 값만 업데이트하는 방법과 매우 유사합니다.
기본적인 이해가 끝났으니 이제 래퍼에 대한 속성 정의와 같은 개인적인 지식과 필요할 때 활용할 수 있는 다른 것들을 추가할 수 있습니다. 사람들은 캐싱, 로깅, 단위 테스트, 인증/인증(플라스크와 장고에서) 등 다양한 용도로 데코레이터를 사용하는 경향이 있습니다.
이제 유용하다고 생각되는 기능을 마음껏 가지고 놀며 꾸미는 것은 여러분의 손에 달려 있습니다. 하지만 지금까지 논의한 내용에 한 가지 더 추가하고 싶습니다. 데코레이터가 x=x+1과 비슷하다고 말한 것을 기억하시나요? 실제로 비슷하며, 이 과정에서 함수에 추가한 모든 메타데이터(예: doc_strings)가 손실됩니다. 아래 코드를 실행하여 직접 확인해 보세요.
def time_decorator_function(func):
def wrapper(*args, **kwargs):
"""
This just wraps over the input func
"""
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f’Time taken {func.__name__}: {end-start}’)
return result
return wrapper
@time_decorator_function
def add_directly(k):
"""
Adds the numbers between 1 and k directly.
"""
result = 0
for i in range(k):
result+=i+1
return result
@time_decorator_function
def use_expression(k):
return k*(k+1)/2
print(add_directly.__name__)
print(add_directly.__doc__)
메타데이터를 유지하기 위해 래퍼 함수에 원래 메타데이터를 추가하는 래퍼 함수에 functools.wraps 데코레이터를 사용하는 경우가 많습니다. 예를 들어 아래 코드 블록을 참조하세요.
import functools
def custom_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
pass
return wrapper
이제 데코레이터를 직접 추가하고 속성을 직접 확인해 보세요.
'Python' 카테고리의 다른 글
파이썬에서 프로그램을 종료하는 3가지 방법 (3) | 2024.01.17 |
---|---|
HTML 버튼에서 Python 스크립트를 실행하는 방법 (0) | 2024.01.14 |
데이터 과학을 한다면 알아야 할 파이썬 라이브러리 (2) | 2024.01.09 |
파이썬 코드의 가독성을 높이는 방법 (34) | 2024.01.08 |
파이썬에서 파일을 다루는 방법 (26) | 2024.01.05 |