... visits
파이썬 코드를 보다보면 간혹 정의된 함수 위에 @
가 붙은 짧은 문장, 의미상으로는 함수명을 같기도한 것들이 있는 것을 본적이 많을
것이다.
어떻게 보면 문서화를 위한 Docstring 으로 보일 수도 있는데 이것은 사실 **데코레이터(Decorator)**라고 불리는 함수 표현이다.
이름에서 예상할 수 있듯이 무엇인가 꾸며주는 역할을 할 것 같은데, 정말로 그렇다.
데코레이터는 함수를 꾸며주는(?)는 함수이다. 좀 더 정확하게 말하자면 기존에 정의된 함수의 능력을 확장할 수 있게 해주는 함수이다.
파이썬의 함수는 일급 객체(First class object) 이다. 파이썬 함수의 특징은 다음과 같다.
def greet(name):
return "Hello {}".format(name)
greet_someone = greet
greet_someone("Eunwoo")
> 실행결과
'Hello Eunwoo'
def greeting(name):
def greet_message():
return 'Hello'
return "{} {}".format(greet_message(), name)
greeting("Eunwoo")
> 실행결과
'Hello Eunwoo'
def change_name_greet(func):
name = "Narae"
return func(name)
change_name_greet(greet)
> 실행결과
'Hello Narae'
def uppercase(func):
def wrapper(name):
result = func(name)
return result.upper()
return wrapper
new_greet = uppercase(greet)
new_greet("eunwoo")
> 실행결과
'HELLO EUNWOO'
@staticmethod
, @classmethod
로 소개됨데코레이터 표현법을 보기전에 먼저 데코레이터와 같은 역할을 하는 함수를 만들어보자.
class Greet(object):
current_user = None
def set_name(self, name):
if name == 'admin':
self.current_user = name
else:
raise Exception("권한이 없네요")
def get_greeting(self, name):
if name == 'admin':
return "Hello {}".format(self.current_user)
greet = Greet()
greet.set_name('eunwoo')
> 실행결과
Exception Traceback (most recent call last)
<ipython-input-34-04060cea2324> in <module>()
12
13 greet = Greet()
---> 14 greet.set_name('eunwoo')
<ipython-input-34-04060cea2324> in set_name(self, name)
5 self.current_user = name
6 else:
----> 7 raise Exception("권한이 없네요")
8
9 def get_greeting(self, name):
Exception: 권한이 없네요
이 클래스의 메소드들은 전달받은 name
인자가 admin 일때만 수행하는 부분들을 갖고 있다.
공통적으로 사용하는 부분을 따로 떼어낼 수 있을 것 같다.
def is_admin(user_name):
if user_name != 'admin':
raise Exception("권한이 없다니까요")
class Greet(object):
current_user = None
def set_name(self, name):
is_admin(name)
self.current_user = name
def get_greeting(self, name):
is_admin(name)
return "Hello {}".format(self.current_user)
greet = Greet()
greet.set_name('admin')
greet.get_greeting('eunwoo')
> 실행결과
Exception Traceback (most recent call last)
<ipython-input-35-3f79f20b3c05> in <module>()
15 greet = Greet()
16 greet.set_name('admin')
---> 17 greet.get_greeting('eunwoo')
<ipython-input-35-3f79f20b3c05> in get_greeting(self, name)
10
11 def get_greeting(self, name):
---> 12 is_admin(name)
13 return "Hello {}".format(self.current_user)
14
<ipython-input-35-3f79f20b3c05> in is_admin(user_name)
1 def is_admin(user_name):
2 if user_name != 'admin':
----> 3 raise Exception("권한이 없다니까요")
4
5 class Greet(object):
Exception: 권한이 없다니까요
조금 더 좋아졌다. 하지만 데코레이터를 쓰면 더 좋은 코드가 될 수 있다.
def is_admin(func):
def wrapper(*args, **kwargs):
"""
decorate
"""
if kwargs.get('username') != 'admin':
raise Exception("아 진짜 안된다니까 그러네..")
return func(*args, **kwargs)
return wrapper
class Greet(object):
current_user = None
@is_admin
def set_name(self, username):
self.current_user = username
@is_admin
def get_greeting(self, username):
"""
greeting
:param username: 이름
:type username: string
"""
return "Hello {}".format(self.current_user)
greet = Greet()
greet.set_name(username='admin')
greet.get_greeting(username='admin')
> 실행결과
'Hello admin'
greet.get_greeting(username='eunwoo')
> 실행결과
Exception Traceback (most recent call last)
<ipython-input-61-2aed1f705388> in <module>()
----> 1 greet.get_greeting(username='eunwoo')
<ipython-input-60-f1529a570fbc> in wrapper(*args, **kwargs)
5 """
6 if kwargs.get('username') != 'admin':
----> 7 raise Exception("아 진짜 안된다니까 그러네..")
8 return func(*args, **kwargs)
9 return wrapper
Exception: 아 진짜 안된다니까 그러네..
위와 같이 데코레이터를 만들게 되면 원래 함수의 속성들이 사라지는 문제점이 발생한다.
이것을 보완하기 위해 functools
모듈에는 데코레이터를 위한 데코레이터 @wraps
가 있다.
greet.get_greeting.__name__
# 'wrapper'
greet.set_name.__doc__
# '\n decorate\n '
메소드의 __name__
, __doc__
속성을 확인해보면, wrapper 함수의 속성이 나오게 된다.
from functools import wraps
def is_admin(func):
@wraps(func)
def wrapper(*args, **kwargs):
if kwargs.get('username') != 'admin':
raise Exception("아 진짜 안된다니까 그러네..")
return func(*args, **kwargs)
return wrapper
class Greet(object):
current_user = None
@is_admin
def set_name(self, username):
self.current_user = username
@is_admin
def get_greeting(self, username):
"""
greeting
:param username: 이름
:type username: string
"""
return "Hello {}".format(self.current_user)
greet = Greet()
greet.get_greeting.__name__
# 'get_greeting'
greet.get_greeting.__doc__
# '\n greeting\n \n :param username: \xec\x9d\xb4\xeb\xa6\x84 \n :type username: string\n '
데코레이터에도 추가 인자를 넘겨줄 수 있다.
이때는 데코레이터를 정의하는 것이 아니라 데코레이터를 만들어주는 함수를 정의한다고 볼 수 있다.
def add_tags(tag_name):
print("Gernerate decorator")
def set_decorator(func):
def wrapper(username):
return "<{0}>{1}</{0}>".format(tag_name, func(username))
return wrapper
return set_decorator
@add_tags("div")
def greeting(name):
return "Hello " + name
print greeting("Jonnung")
> 실행결과
<div>Hello Jonnung</div>
class Sample(object):
def __init__(self):
print("init")
def __call__(self):
print("call")
sample = Sample()
# init
sample()
# call
클래스의 인스턴스를 함수처럼 호출하기 위해서 클래스에 __call__
이라는 매직 메소드를 정의했다.
이 원리를 이용해서 클래스를 데코레이터로 구현할 수 있다.
from functools import wraps
class OnlyAdmin(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
name = kwargs.get('name').upper()
self.func(name)
@OnlyAdmin
def greet(name):
print("Hello {}".format(name))
greet(name='Eunwoo')
> 실행결과
Hello EUNWOO