Excel & IT Info

아이엑셀러 닷컴, 엑셀러TV

Python

파이썬에서 데코레이터 단순화하기

권현욱(엑셀러) 2024. 1. 12. 20:00
반응형

파이썬은 요즘 배울 수 있는 간단한 언어지만 올바르게 이해하려면 주의가 필요한 영역이 있습니다. 데코레이터(decorator)도 그 중 하나입니다. 데코레이션을 이해하기 위한 단계별 접근 방식을 소개합니다.

 

권현욱(엑셀러) | 아이엑셀러 닷컴 대표 · Microsoft Excel MVP · Excel 솔루션 프로바이더 · 작가

 

(이미지: 아이엑셀러 닷컴)

 

※ 이 글은 아래 기사 내용을 토대로 작성되었습니다만, 필자의 개인 의견이나 추가 자료들이 다수 포함되어 있습니다.


  • 원문: 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

 

이제 데코레이터를 직접 추가하고 속성을 직접 확인해 보세요.

 

Excel과 VBA의 모든 것 아이엑셀러 닷컴 · 강사들이 숨겨 놓고 보는 엑셀러TV