[Python] 데코레이터 (decorator)

cdwde·2021년 6월 5일
1
post-custom-banner

🎈 decorator

  • 함수를 받아 명령을 추가한 뒤 이를 다시 함수의 형태로 반환하는 함수

  • 함수의 내부를 수정하지 않고 기능에 변화를 주고 싶을 때 사용

  • 함수의 전처리나 후처리에 대한 필요가 있을 때 사용

  • 반복을 줄이고 메소드나 함수의 책임을 확장

데코레이터 사용 X

def decorator_function(original_function):
  def wrapper_function():
    print(f'{original_function.__name__} 함수가 호출되기 전 입니다')
    return original_function()
  return wrapper_function
  
def display_1():
  print('display_1 함수가 실행됐습니다')

def display_2():
  print('display_2 함수가 실행됐습니다')
  
display_1 = decorator_function(display_1)	#1
display_2 = decorator_function(display_2)	#2

display_1()
display_2()

#출력 결과
#display_1 함수가 호출되기 전 입니다
#display_1 함수가 실행됐습니다
#display_2 함수가 호출되기 전 입니다
#display_2 함수가 실행됐습니다
  • #1, #2처럼 잘 쓰지 않고 @<데코레이터함수이름> 쓰는 간단한 구문 사용

데코레이터 사용

def decorator_function(original_function):
  def wrapper_function():
    print(f'{original_function.__name__} 함수가 호출되기 전 입니다')
    return original_function()
  return wrapper_function
  
@decorator_function
def display_1():
  print('display_1 함수가 실행됐습니다')
  
@decorator_function
def display_2():
  print('display_2 함수가 실행됐습니다')

display_1()
display_2()

#출력 결과
#display_1 함수가 호출되기 전 입니다
#display_1 함수가 실행됐습니다
#display_2 함수가 호출되기 전 입니다
#display_2 함수가 실행됐습니다

타입 에러 (wrapper_function은 인자를 받지 않는데 2개의 인자 전달)

def decorator_function(original_function):
  def wrapper_function():
    print(f'{original_function.__name__} 함수가 호출되기 전 입니다')
    return original_function
  return wrapper_function
  
@decorator_function
def display_info(name, age):
  print('display (f{name, age}) 함수가 실행됐습니다')


display_info('name' , 10)


#Traceback (most recent call last):
#  File "c:/Users/chldp/OneDrive/바탕 화면/파이썬/t.py", line 12, #in <module>
#    display_info('name' , 10)
#TypeError: wrapper_function() takes 0 positional arguments but #2 were given

위의 타입 에러 고치기

def decorator_function(original_function):
  def wrapper_function(*args, **kwargs):
    print(f'{original_function.__name__} 함수가 호출되기 전 입니다')
    return original_function(*args, **kwargs)
  return wrapper_function

@decorator_function
def display():
  print('display 함수가 실행됐습니다')

@decorator_function
def display_info(name, age):
  print(f'display({name}, {age}) 함수가 실행됐습니다')


display()
display_info('name' , 10)

#display 함수가 호출되기 전 입니다
#display 함수가 실행됐습니다
#display_info 함수가 호출되기 전 입니다
#display(name, 10) 함수가 실행됐습니다

클래스 형식 사용하기
클래스 형식의 데코레이터는 그다지 많이 사용되지 않고 보통 함수 형식이 많이 사용

class DecoratorClass:
  def __init__(self, original_finction):
    self.original_finction = original_finction
  
  def __call__(self, *args, **kwargs):
    print(f'{self.original_finction.__name__} 함수가 호출되기 전 입니다')
    return self.original_finction(*args, **kwargs)

@DecoratorClass
def display():
  print('display 함수가 실행됐습니다')

@DecoratorClass
def display_info(name, age):
  print(f'display({name}, {age}) 함수가 실행됐습니다')
  

display()
display_info('name' , 10)
  
#display 함수가 호출되기 전 입니다
#display 함수가 실행됐습니다
#display_info 함수가 호출되기 전 입니다
#display(name, 10) 함수가 실행됐습니다

🎈 데코레이터의 구조

함수로 만드는 데코레이터

def out_func(func):	#기능을 추가할 함수를 인자로
  def inner_func(*args, *kwargs):
    return func(*args, **kwargs)
  return inner_func
  1. decorator 역할을 하는 함수 정의하고 이 함수에서 decorator가 적용될 함수를 인자로 받는다

  2. decorator 역할을 하는 함수 내부에 또 함수를 선언(nested function)하여 여기에 추가적인 작업을 선언

  3. nested function을 return

예시

def decorator(func):
  def wrapper(*args, **kwargs):
    print('전처리')
    print(func(*args, **kwargs)
  return wrapper


@decorator
def exmaple():
  return '함수'

example()

#전처리
#함수
#후처리

클래스로 만드는 데코레이터 구조

class Decorator:
  def __init__(self, function):
    self.function = function
  
  def __call__(self, *args, **kwargs):
    return self.function(*args, **kwargs)
  • __call__ 함수로 decorator 형식 정의

예시

class Decorator:
  def __init__(self, function):
    self.function = function
  
  def __call__(self, *args, **kwargs):
    print('전처리')
    print(self.function(*args, **kwargs)
    print('후처리')
    
@Decorator
def example():
  return '클래스'

example()

#전처리
#클래스
#후처리

🎈 데코레이터 이용해서 로깅 기능 만들기

import datetime
import time

def my_logger(original_function):
  import logging
  logging.basicConfig(filename='{}.log' .format(original_function.__name__, level=logging.INFO))
  
  def wrapper(*args, **kwargs):
    timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M')
    logging.info(
    '[{}] args - {}, kwargs - {}' .format(timestamp, args, kwargs))
    return original_function(*args, **kwargs)
    
  return wrapper
  
def my_timer(original_function):
  import time
  
  def wrapper(*args, **kwargs):
    t1 = time.time()
    result = original_function(*args, **kwargs)
    t2 = time.time() - t1
    print('{} 함수가 실행된 총 시간 {}초' .format(original_function.__name__, t2))
    return result
    
  return wrapper
  
@my_timer
@my_logger
def display_info(name, age):
  time.sleep(1)
  print(f'display_info({name}, {age}) 함수가 실행됐습니다')
  
display_info('name', 10)

#display_info(name, 10) 함수가 실행됐습니다
#wrapper 함수가 실행된 총 시간 1.015899896621704초
  • 복수의 데코레이터를 사용하면 아래쪽 데코레이터부터 실행
  • 위의 코드는 my_logger가 먼저 실행되고 my_timer에게 wrapper 함수를 인자로써 리턴하기 때문에
    출력 결과가 저렇게(wrapper 함수가 실행된 ~) 나옴

functools 모듈의 wraps 데코레이터 사용

from functools import wraps
import datetime
import time

def my_logger(original_function):
  import logging
  logging.basicConfig(filename='{}.log' .format(original_function.__name__, level=logging.INFO))
  
  @wraps(original_function)
  def wrapper(*args, **kwargs):
    timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M')
    logging.info(
    '[{}] args - {}, kwargs - {}' .format(timestamp, args, kwargs))
    return original_function(*args, **kwargs)
    
  return wrapper
  
def my_timer(original_function):
  import time
  
  @wraps(original_function)
  def wrapper(*args, **kwargs):
    t1 = time.time()
    result = original_function(*args, **kwargs)
    t2 = time.time() - t1
    print('{} 함수가 실행된 총 시간 {}초' .format(original_function.__name__, t2))
    return result
    
  return wrapper
  
@my_timer
@my_logger
def display_info(name, age):
  time.sleep(1)
  print(f'display_info({name}, {age}) 함수가 실행됐습니다')
  
display_info('name', 10)


#display_info(name, 10) 함수가 실행됐습니다
#display_info 함수가 실행된 총 시간 1.0032978057861328초

참고
https://hckcksrl.medium.com/python-%EB%8D%B0%EC%BD%94%EB%A0%88%EC%9D%B4%ED%84%B0-decorator-980fe8ca5276
https://bluese05.tistory.com/30
http://schoolofweb.net/blog/posts/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%8D%B0%EC%BD%94%EB%A0%88%EC%9D%B4%ED%84%B0-decorator/

post-custom-banner

0개의 댓글