Python Decorator Basics

알아야 할 것들..

  • 파이썬에서 함수는 객체이다. 즉 함수도 다른 객체들처럼 다른 함수의 인자가 될 수 있다.
  • 함수 안에 함수를 정의할 수 있고 새로 정의한 함수를 리턴할 수도 있다. 단, 부모함수가 실행되기 전에는 내부 함수는 정의되지 않으며, 부모 함수의 로컬 스코프 내에서만 실행 할 수 있다.

데코레이터

단순히 말하자면 파이썬 데코레이터는 어떤 함수를 감싸서(wrap), 그 함수의 행동을 수정하는 함수인데, 파이썬에서는 Syntactic Sugar를 더해 데코레이터를 타겟 함수에 쉽게 적용하도록 만들어 준다.

def my_decorator(func):
  def my_wrapper():
    # Wrapper는 데코레이터가 받은 함수의 행동 또는 인자를 수정한다.
    # 이 경우 원 함수를 두 번 실행한다.
    func()
    func()
  # 데코레이터는 Wrapper를 리턴한다.
  return my_wrapper

# 데코레이터가 적용된 함수는 사실 my_wrapper(say_hello)와 같다.
@my_decorator
def say_hello():
    print("Hello?")
        
>> say_hello() # 이 실행은 사실 my_wrapper(say_hello)()와 같다.
"Hello?"
"Hello?"

원함수에 입력 인자가 있는 경우

원 함수에 입력 인자가 있는 경우 데코레이터의 wrapper 함수도 같은 인자를 받게 설정해야 한다.

하지만 이 경우 데코레이터의 타겟 함수가 같은 수의 인자를 받는 함수만 수정할 수 있게끔 제한된다.

해결책은 *args, **kwargs 인자를 wrapper 함수에 적용하는 것.

def my_decorator(func):
  def my_wrapper(*args, **kwargs):
    # wrapper가 받은 임의의 인자를 그대로 원 함수에 전달한다.
    func(*args, **kwargs)
    func(*args, **kwargs)

  return my_wrapper

@my_decorator
def say_hello():
    print("Hello?")

@my_decorator
def say_name(name):
  print(f"Hello {name}?")
  
>> say_hello()
"Hello?"
"Hello?"

>> say_name("Jun")
"Hello Jun?"
"Hello Jun?"

원함수의 리턴값 리턴해주기

단순하게, wrapper가 원함수를 실행하고 결과를 같이 리턴해 주면 된다.

def my_decorator(func):
  def my_wrapper(*args, **kwargs):
    # wrapper가 받은 임의의 인자를 그대로 원 함수에 전달한다.
    func(*args, **kwargs)
    return func(*args, **kwargs)

  return my_wrapper

원함수의 메타정보 보존해주기

함수가 데코레이터에 의해 wrapped 된 경우 해당 함수의 메타정보는 더이상 그 함수를 가리키지 못한다.

이 경우 functool 빌트인 라이브러리에 정의된 데코레이터 @functool.wraps를 이용해서 데코레이터 정의에서 wrapper를 데코레이팅 해주자.

예를들어,

def my_decorator(func):
  def my_wrapper(*args, **kwargs):
    func(*args, **kwargs)
    func(*args, **kwargs)

  return my_wrapper
  
import functools
def better_decorator(func):
  @functools.wraps(func)
  def my_wrapper(*args, **kwargs):
    func(*args, **kwargs)
    func(*args, **kwargs)

  return my_wrapper


@my_decorator
def say_name(name):
  print(f"Hello {name}?")

@my_decorator2
def say_name2(name):
  print(f"Hello {name}?")
  
>> say_name.__name__
'my_wrapper'

>> say_name2.__name__
'say_name2'

Reference

  1. https://realpython.com/primer-on-python-decorators/

좋은 웹페이지 즐겨찾기