python语言本身具有丰富的功能和表达语法,其中修饰器是一个非常有用的功能。在设计模式中,decorator能够在无需直接使用子类的方式来动态地修正一个函数,类或者类的方法的功能。当你希望在不修改函数本身的前提下扩展函数的功能时非常有用。
简单地说,decorator就像一个wrapper一样,在函数执行之前或者之后修改该函数的行为,而无需修改函数本身的代码,这也是修饰器名称的来由。
关于函数
在Python中,函数是first class citizen,函数本身也是对象,这意味着我们可以对函数本身做很多有意义的操作。
将函数赋值给变量:
defgreet(name):return "hello"+name
greet_someone=greetprint greet_someone("John")#Outputs: hello John
函数内定义函数:
defgreet(name):defget_message():return "Hello"result= get_message()+namereturnresultprint greet("John")#Outputs: Hello John
函数可以作为参数传给其他函数:
defgreet(name):return "Hello" +namedefcall_func(func):
other_name= "John"
returnfunc(other_name)printcall_func(greet)#Outputs: Hello John
函数可以返回其他函数(函数产生函数):
defcompose_greet_func():defget_message():return "Hello there!"
returnget_message
greet=compose_greet_func()printgreet()#Outputs: Hello there!
内部函数可以访问外部包scope(enclosing scope)
defcompose_greet_func(name):defget_message():return "Hello there"+name+"!"
returnget_message
greet= compose_greet_func("John")printgreet()#Outputs: Hello there John!
需要注意的是:这种情况下python仅仅允许"只读"访问外部scope的变量
开始创作我们的decorator
函数的修饰器就是已知函数的wrapper.将上述函数的好功能运用起来就能制作我们的decorator.
defget_text(name):return "lorem ipsum, {0} dolor sit amet".format(name)defp_decorate(func):deffunc_wrapper(name):return "
{0}
".format(func(name))returnfunc_wrappermy_get_text=p_decorate(get_text)print my_get_text("John")#
Outputs lorem ipsum, John dolor sit amet
这就是我们的第一个修饰器。一个函数接收另一个函数作为参数,并且产生一个新的函数,修正参数函数的功能并添加新功能,并且返回一个"generated"新函数,这样我们后面就可以在任何地方使用这个新创建的函数了。我们也可以将修饰器函数直接赋值给参数函数名本身,这样就覆盖了原来的函数!
get_text =p_decorate(get_text)print get_text("John")#Outputs lorem ipsum, John dolor sit amet
另外一点需要注意的是:被修饰的函数get_text具有一个name参数,我们必须在wrapper函数中传入那个参数。
python的修饰符语法糖
在上面的例子中我们通过get_text=p_decorate(get_text)的方式覆盖了get_text从而形成了有新功能的同名函数,这个显得有点啰嗦,python提供了简洁清晰的对应语法。比如:
defp_decorate(func):deffunc_wrapper(name):return "
{0}
".format(func(name))returnfunc_wrapper@p_decoratedefget_text(name):return "lorem ipsum, {0} dolor sit amet".format(name)print get_text("John")#Outputs
lorem ipsum, John dolor sit amet
在上面的例子代码中,@符号后面的是修饰器本身,紧跟后面的则是将被修饰的函数(将隐含着赋值覆盖操作)。这种语法等价于使用@后面的修饰器先对get_text修饰,并且返回产生的新函数替代被修饰的函数名。后面直接用被修饰的函数名调用,但是却有了新的功能!
现在,我们希望再添加两个其他的函数来修饰get_text分别再增加一个div和strong tag
defp_decorate(func):deffunc_wrapper(name):return "
{0}
".format(func(name))returnfunc_wrapperdefstrong_decorate(func):deffunc_wrapper(name):return " {0}".format(func(name))returnfunc_wrapperdefdiv_decorate(func):deffunc_wrapper(name):return "get_text =div_decorate(p_decorate(strong_decorate(get_text)))#等价于:
@div_decorate
@p_decorate
@strong_decoratedefget_text(name):return "lorem ipsum, {0} dolor sit amet".format(name)print get_text("John")#Outputs
lorem ipsum, John dolor sit amet
需要注意的是修饰器的顺序是有关系的。如果顺序不同,则结果也不同。
method修饰
python中类的方法是一个首参数为self指针的函数。我们可以和普通函数一样去做修饰,但是需要注意的是必须在wrapper函数中考虑self指针参数。
defp_decorate(func):deffunc_wrapper(self):return "
{0}
".format(func(self))returnfunc_wrapperclassPerson(object):def __init__(self):self.name= "John"self.family= "Doe"@p_decoratedefget_fullname(self):return self.name+" "+self.family
my_person=Person()print my_person.get_fullname()
一个更好的方案是调整代码使得我们的修饰器对于函数或者method同样适用。这可以通过通过将args和*kwargs放到wrapper函数中作为参数来实现,这样可以接受任意个数的参数或者keyword型参数。
defp_decorate(func):def func_wrapper(*args, **kwargs):return "
{0}
".format(func(*args, **kwargs))returnfunc_wrapperclassPerson(object):def __init__(self):self.name= "John"self.family= "Doe"@p_decoratedefget_fullname(self):return self.name+" "+self.family
my_person=Person()print my_person.get_fullname()
向decorator传入参数
deftags(tag_name):deftags_decorator(func):deffunc_wrapper(name):return "{1}{0}>".format(tag_name, func(name))returnfunc_wrapperreturntags_decorator
@tags("p")defget_text(name):return "Hello"+nameprint get_text("John")#Outputs
Hello John
在这个例子中,貌似又更加复杂了一点,但是带来了更多的灵活性。decorator必须仅接受一个被修饰的函数为参数,这也是为什么我们必须再外包裹一层从而接受那些额外的参数并且产生我们的decorator的原因。这个例子中tags函数是我们的decorator generator
调试decorated function
从上面的描述可知,decorators负责包裹被修饰的函数,这带来一个问题就是如果要调试代码可能有问题,因为wrapper函数并不会携带原函数的函数名,模块名和docstring等信息,比如基于以上的例子,如果我们打印get_text.__name__则返回func_wrapper而不是get_text,原因就是__name__,__doc__,__module__这些属性都被wrapper函数所(func_wrapper)重载。虽然我们可以手工重置(在func_wrapper),但是python提供了更好的办法:
functools
functools模块包含了wraps函数。wraps也是一个decorator,但是仅仅用于更新wrapping function(func_wrapper)的属性为原始函数的属性(get_text),看下面的代码:
from functools importwrapsdeftags(tag_name):deftags_decorator(func):
@wraps(func)deffunc_wrapper(name):return "{1}{0}>".format(tag_name, func(name))returnfunc_wrapperreturntags_decorator
@tags("p")defget_text(name):"""returns some text"""
return "Hello"+nameprint get_text.__name__ #get_text
print get_text.__doc__ #returns some text
print get_text.__module__ #__main__
何时使用decorator?
在上面的例子中仅仅罗列了修饰器的基础用法,实际上这个机制是非常强大有用的,总的来说,decorator在你希望在不修改函数本身代码的前提下扩展函数的功能时非常有用。
一个经典的例子timeout修饰函数:
https://wiki.python.org/moin/PythonDecoratorLibrary#Function_Timeout
timeout修饰符产生函数的定义:
importsignalimportfunctoolsclass TimeoutError(Exception): pass
def timeout(seconds, error_message = 'Function call timed out'):defdecorated(func):def_handle_timeout(signum, frame):raiseTimeoutError(error_message)def wrapper(*args, **kwargs):
signal.signal(signal.SIGALRM, _handle_timeout)
signal.alarm(seconds)try:
result= func(*args, **kwargs)finally:
signal.alarm(0)returnresultreturnfunctools.wraps(func)(wrapper)return decorated
使用:
importtime
@timeout(1, 'Function slow; aborted')defslow_function():
time.sleep(5)
https://www.thecodeship.com/patterns/guide-to-python-function-decorators/
https://wiki.python.org/moin/PythonDecoratorLibrary