TIL 6. Decorator

jiffydev·2020년 9월 19일
0

Decorator

1. 정의

파이썬 공식문서에서는 데코레이터를 굉장히 간결하게 정의했다. 이것만 봐도 데코레이터가 뭘 하는 존재인지는 알 수 있지만 막상 실제로 사용하자니 헷갈리는게 많은 것도 사실이다.

A function returning another function, usually applied as a function transformation using the @wrapper syntax. Common examples for decorators are classmethod() and staticmethod().
The decorator syntax is merely syntactic sugar, the following two function definitions are semantically equivalent:

def f(...):
    ...
f = staticmethod(f)
@staticmethod
def f(...):
    ...

매우 심플하다. 함수인데 다른 함수를 리턴하는 함수
그러면 그냥 함수를 중첩해서 실행하면 되는데 굳이 데코레이터라는 이름까지 붙여가면서 나같은 초보자의 머리를 아프게 하는걸까?

2. 용도

상기 정의에도 나와있듯, 데코레이터는 syntactic sugar이다. 없어도 지장이 생기는건 아니지만 사용하면 코드가 간결해지고 아름다워진다. 특히 한 함수를 다른 함수 안에서 실행시켜야 하는데 이것이 여러 곳에서 실행되어야 한다면 더욱 유용하다. 왜냐하면 다른 함수 안에서 실행시켜야 한다는 것을 잊어버릴 수도 있고 후임자가 이를 캐치하지 못할 가능성도 높기 때문이다.

def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

def say_whee():
    print("Whee!")

say_whee = my_decorator(say_whee)

>>> say_whee()
Something is happening before the function is called.
Whee!
Something is happening after the function is called.

위 예는 간단하지만, 만약 say_whee()함수를 여러 곳에서 사용하는데 반드시 print("Something is happening before the function is called.")print("Something is happening after the function is called.") 사이에 출력시키고 싶다면 매번 my_decorator()안에서 실행시켜야 한다. 만약 그냥 say_whee()만 실행시키면 "Whee!"만 출력되기 때문이다. 참으로 잊어버리기 좋은 구조이다. 따라서 데코레이터를 사용하면 say_whee()만 실행시켜도 알아서 my_decorator()함수 안에서 실행시켜 준다.

def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

@my_decorator
def say_whee():
    print("Whee!")

>>> say_whee()
Something is happening before the function is called.
Whee!
Something is happening after the function is called.

결국 데코레이터는 내용은 단순한 함수이지만 함수를 재사용할 수 있게 만들기 때문에 특별히 이름을 붙인 것이다. 또한 데코레이터를 모듈로 만들면 다른 파일에서도 import 해서 데코레이터로 사용하는 것도 가능하다.

3. 참고

3-1. 인자를 넘겨주어야 할 때

위 예제에서는 인자가 필요 없는 함수였지만, 세상에는 인자를 필요로 하는 함수가 더 많을 것이다. 인자를 넘겨주어야 할 때는 추가해야 할 요소가 있다.

def do_twice(func):
    def wrapper_do_twice():
        func()
        func()
    return wrapper_do_twice
    
@do_twice
def greet(name):
    print(f"Hello {name}")    

여기서 greet(world)를 실행하면

>>> greet("World")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: wrapper_do_twice() takes 0 positional arguments but 1 was given

래퍼 함수는 인자를 받지 않게 설정되어 있는데 인자가 주어졌다면서 에러가 난다. 그러므로 래퍼 함수에서도 인자를 받을 수 있도록 해야 하는데, 여기서 사용하는 것이 *args **kwargs 이다. 기본적으로 래퍼 함수와 안에서 실행되는 함수에 가변길이 인자를 넣어주면 된다.

def do_twice(func):
    def wrapper_do_twice(*args, **kwargs):
        func(*args, **kwargs)
        func(*args, **kwargs)
    return wrapper_do_twice
    
@do_twice
def greet(name):
    print(f"Hello {name}")    

>>> greet("World")
Hello World
Hello World

이와같이 문제 없이 실행되는 것을 볼 수 있다.

3-2. 안에서 실행되는 함수에 리턴값이 있을 때

함수를 만들면 대부분 리턴값이 존재한다. 위의 예처럼 print만 있는 경우는 많지 않을 것이다. 그래서 리턴값도 실행되겠거니 하고 함수를 실행하면 아래와 같은 결과가 나온다.

@do_twice
def return_greeting(name):
    print("Creating greeting")
    return f"Hi {name}"
    
>>> hi_adam = return_greeting("Adam")
Creating greeting
Creating greeting
>>> print(hi_adam)
None   

print문은 잘 출력되는데 리턴값은 출력되지 않았고, 우리가 리턴하고 싶은 값은 아예 None값이 되어버렸다. 이는 데코레이터 함수를 보면 알 수 있는데 wrapper_do_twice()함수를 잘 보면 return이 없는 것을 알 수 있다. 그렇기 때문에 이 함수는 명시적으로 값을 되돌려주지 않고, 이로 인해 출력해도 None으로밖에 나오지 않는다.
그러므로 리턴해야할 값이 있으면 래퍼 함수에 반드시 리턴을 명시해 주어야 한다. 그래서 다음과 같이 수정하면 리턴값이 나오는 것을 볼 수 있다.

def do_twice(func):
    def wrapper_do_twice(*args, **kwargs):
        func(*args, **kwargs)
        return func(*args, **kwargs)
    return wrapper_do_twice
    
@do_twice
def return_greeting(name):
    print("Creating greeting")
    return f"Hi {name}"

>>>print(return_greeting("adam"))
Creating greeting
Creating greeting
'Hi Adam'
profile
잘 & 열심히 살고싶은 개발자

0개의 댓글