[번역] python의 함수 decorators 가이드

Jeongkuk Seo
sjk5766
Published in
12 min readOct 21, 2018

우선, 이 글은 아래 링크를 번역한 것입니다. 번역이 저질이라 그렇지 영어가 되시는 분들은 원문을 읽어보셔도 됩니다. (this post is translated the link below. if this post make problem, please let me know, i will remove this post.)

python은 강력한 기능들과 표현적인 구문들을 가지고 있습니다. 그 중 제가 제일 선호하는 것은 decorators 입니다. 디자인 패턴 영역에서 decorator는 method or class의 subclass를 직접 사용하지 않아도, 함수의 기능을 동적으로 추가할 수 있습니다. 이런 개념은 우리가 함수의 기능 수정 없이 확장시키고 싶을 때 필요할 수 있습니다. 우리는 decorator 패턴을 구현할 수 있지만 특히python에서는 표현적인 구문과 기능들을 제공함으로 써 decorator 구현을 도와줍니다.

이 post에서는 python 함수 decorators를 깊이 있게 다루며, 개념 이해를 위한 여러 예제가 수반됩니다. 모든 예제는 python 2.7 에서 동작하며 조금 다른 문법으로 python 3.x에 반영할 수 있습니다.

본질적으로, decorators는 wrapper로써 동작하며, 원본 함수를 수정할 필요 없이 원본 함수의 실행 전/후의 동작을 변경할 수 있습니다.

함수에 대해 알아야 할 것

깊게 들어가기 전에, 명확히 알아야 할 전제조건이 있습니다. python에서 함수는 일급시민이며, 일급객체입니다. 이 말은 아래에 나오는 여러 유용한 작업들을 할 수 있다는 의미입니다.

함수를 변수에 대입할 수 있다.

def greet(name):
return "hello "+name

greet_someone = greet
print greet_someone("John")

# Outputs: hello John

함수 내부에 다른 함수를 정의할 수 있다.

def greet(name):
def get_message():
return "Hello "

result = get_message()+name
return result

print greet("John")

# Outputs: Hello John

함수는 다른 함수의 인자로 전달될 수 있다.

def greet(name):
return "Hello " + name

def call_func(func):
other_name = "John"
return func(other_name)

print call_func(greet)

# Outputs: Hello John

함수는 다른 함수에 의해 리턴될 수 있다.

다른 말로 함수는 다른 함수를 생성 할 수 있습니다.

def compose_greet_func():
def get_message():
return "Hello there!"

return get_message

greet = compose_greet_func()
print greet()

# Outputs: Hello there!

내부 함수는 둘러싸인 범위(enclosing scope)에 대해 접근이 가능하다.

보통 closure로 알려져 있습니다. decorators를 알아가는 과정에서 우연히 만난 강력한 패턴입니다. 내부 함수의 근접한 scope로부터 name 인자를 읽기 위해 위의 예제로부터 어떻게 변경했는지 봅시다.

def compose_greet_func(name):
def get_message():
return "Hello there "+name+"!"

return get_message

greet = compose_greet_func("John")
print greet()

# Outputs: Hello there John!

Decorators의 구성

함수 decorators는 존재하는 함수에 대한 간단한 wrappers입니다. 위에서 언급한 아이디어를 모아서 decorators를 만들 수 있습니다. 아래 예제에서, p 태그에 의해 문자열을 출력하는 함수를 wrap 하는 것을 고려해보겠습니다.

def get_text(name):
return "lorem ipsum, {0} dolor sit amet".format(name)

def p_decorate(func):
def func_wrapper(name):
return "<p>{0}</p>".format(func(name))
return func_wrapper

my_get_text = p_decorate(get_text)

print my_get_text("John")

# <p>Outputs lorem ipsum, John dolor sit amet</p>

이게 우리의 첫 번째 decorator 입니다. 함수는 다른 함수를 인자로 전달 받고 기존 함수의 기능에 추가적인 작업을 하는 새로운 함수를 생성하고 리턴합니다. get_text 함수를 p_decorate로 꾸미고 싶다면 우린 단지 p_decorate의 리턴 값을 get_text에 할당하면 됩니다.

get_text = p_decorate(get_text)

print get_text("John")

# Outputs lorem ipsum, John dolor sit amet

Python’s Decorator 문법

python은 문법적인 sugar(컴퓨터 용어 sugar란 기존 기능을 좀 더 쉽게 쓰기 위한 문법적 방법)를 통해 programmer들에게 좀 더 명확하고 보기 좋은 decorator 방법을 제공합니다. get_text를 decorate 하기 위해 get_text = p_decorator(get_text)와 같은 코드를 쓸 필요가 없습니다. decorated 된 함수 전에 decoration 하는 함수의 이름을 언급하는 방법이 있습니다. decorator 이름과 함께 @ 심볼이 나와야 합니다.

def p_decorate(func):
def func_wrapper(name):
return "<p>{0}</p>".format(func(name))
return func_wrapper

@p_decorate
def get_text(name):
return "lorem ipsum, {0} dolor sit amet".format(name)

print get_text("John")

# Outputs <p>lorem ipsum, John dolor sit amet</p>

이제 우리는 get_text 함수를 div와 string 태그로 감싸는 두 개의 다른 함수를 고려해봅시다.

def p_decorate(func):
def func_wrapper(name):
return "<p>{0}</p>".format(func(name))
return func_wrapper

def strong_decorate(func):
def func_wrapper(name):
return "<strong>{0}</strong>".format(func(name))
return func_wrapper

def div_decorate(func):
def func_wrapper(name):
return "<div>{0}</div>".format(func(name))
return func_wrapper

기본적인 접근방법으론, 아래 코드와 같이 decorating 할 수 있습니다.

get_text = div_decorate(p_decorate(strong_decorate(get_text)))

Python의 decorator 문법으로 동일한 기능을 제공합니다.

@div_decorate
@p_decorate
@strong_decorate
def get_text(name):
return "lorem ipsum, {0} dolor sit amet".format(name)

print get_text("John")

# Outputs <div><p><strong>lorem ipsum, John dolor sit amet</strong></p></div>

여기서 한가지 중요한 것은 decorators의 순서입니다. 만약 위의 예제에서 decorators의 순서가 달라지면 결과도 달라집니다.

Class의 Method Decorating

Python에서 methods는 첫 번째 인자가 객체를 참조(self)하는 함수입니다. wrapper 함수의 인자를 self를 받는 방법으로 동일하게 methods에 대해 decorators를 만들 수 있습니다.

def p_decorate(func):
def func_wrapper(self):
return "<p>{0}</p>".format(func(self))
return func_wrapper

class Person(object):
def __init__(self):
self.name = "John"
self.family = "Doe"

@p_decorate
def get_fullname(self):
return self.name+" "+self.family

my_person = Person()
print my_person.get_fullname()

더 좋은 접근은 우리의 decorator가 함수와 methods 모두에게 유용하도록 만드는 것입니다. 이 방법을 위해 wrapper 함수의 인자로 args 와 *kwargs를 넣고 이를 통해 가변인자와 가변 키워드 인자를 전달받을 수 있습니다.

def p_decorate(func):
def func_wrapper(*args, **kwargs):
return "<p>{0}</p>".format(func(*args, **kwargs))
return func_wrapper

class Person(object):
def __init__(self):
self.name = "John"
self.family = "Doe"

@p_decorate
def get_fullname(self):
return self.name+" "+self.family

my_person = Person()

print my_person.get_fullname()

decorators에게 인자 전달하기

위의 예제들을 돌아보면, 많은 decorators 예제들이 중복되어 있는 것을 알 수 있습니다. 3개의 decorators( div_decorate, p_decorate, strong_decorate)를 살펴보면 name 변수의 string을 서로 다른 태그로 감싸는 것만 다르고 다른 동작은 모두 동일합니다. 우리는 명백하게 더 많은 것을 할 수 있어야만 합니다. tag를 인자로 받아 string을 감싸는 일반적인 구현방법은 없을까요? 제발염!

def tags(tag_name):
def tags_decorator(func):
def func_wrapper(name):
return "<{0}>{1}</{0}>".format(tag_name, func(name))
return func_wrapper
return tags_decorator

@tags("p")
def get_text(name):
return "Hello "+name

print get_text("John")

# Outputs <p>Hello John</p>

위 경우 조금 더 많은 작업을 했습니다. Decorators는 인자로 함수를 받기를 기대하기 때문에, 우리는 추가적인 인자를 받아 우리의 decorator를 즉석에서 만드는 함수를 정의했습니다. 위의 예제에선 tags로 우리의 decorator를 생성합니다.

decorated 된 함수 디버깅

결국 가장 중요한 것은 decorators는 단지 우리가 만든 함수를 wrapping 한다는 것입니다. 이 파트에선, wrapper 함수가 원본 함수의 name, module, docstring을 가지지 않기 때문에 발생하는 문제를 다룹니다. 위의 예제들을 기반으로 할 경우 :

print get_text.__name__
# Outputs func_wrapper

출력은 get_text로 기대했지만, get_text의 __name__, __doc__, __module__의 속성이 이런 warpper 함수들에 의해 오버라이딩 됩니다. 분명하게 우리는 wrapper 함수 안에서 이들을 reset 해줘야 하고, python은 훨씬 좋은 방법을 제공합니다.

Functools 이 대안입니다.

운좋게도 python (version 2.5)는 functools.wraps를 포함하는 functools 모듈을 가지고 있습니다. Wraps는 wrapping 하는 함수의 속성들을 원본 함수의 속성들로 update 해주는 decorator 입니다. 아래 예제가 있습니다.

from functools import wraps

def tags(tag_name):
def tags_decorator(func):
@wraps(func)
def func_wrapper(name):
return "<{0}>{1}</{0}>".format(tag_name, func(name))
return func_wrapper
return tags_decorator

@tags("p")
def get_text(name):
"""returns some text"""
return "Hello "+name

print get_text.__name__ # get_text
print get_text.__doc__ # returns some text
print get_text.__module__ # __main__

이제 get_text의 속성이 제대로 나오는 것을 알 수 있습니다.

decorators는 어디에 쓰나?

decorators 는 우리의 프로그램을 더욱 더 강력하고 우아하게 만들어줍니다. 일반적으로 decorators는 우리가 함수의 기능변경을 원치 않는 상황에서 기능을 확장하고 싶을 때 사용하는 개념입니다.

--

--