之前在看<<Python核心编程>>时遇到decorator时看得云里雾里,然后也没去多管,想到等以后遇到这个问题时再去解决。今天在看Django代码时看到login_required等装饰器时又遇到这个东西了,于是在网上找了一些文档重新学了一下,在此做一个总结。
1. 装饰器含义
装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
2. 装饰器语法
(1)无参数装饰器
def deco(func): print("deco") return func @deco def foo(): print("foo") foo()第一个函数deco是装饰函数,它的参数就是被装饰的函数对象。我们可以在deco函数内对传入的函数对象做一番“装饰”,然后返回这个对象( 记住一定要返回 ,不然外面调用foo的地方将会无函数可用。实际上此时foo=deco(foo)
(2)有参数装饰器
被装饰函数无参数
def deco(argv): def decorator(func): print("decorator") return func print("deco") print(argv) return decorator @deco("123") def foo(): print("foo") foo()第一个函数deco是装饰函数,它的参数是用来“加强装饰”的。由于此函数并非被装饰的函数对象,所以在内部必须至少创建一个接受被装饰函数的函数,然后返回这个对象。实际上此时foo = deco(argv)(foo)
被装饰函数也有参数
from functools import wraps def deco(argv): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): print("wrapper") return func(*args, **kwargs) print("decorator") return wrapper print("deco") print(argv) return decorator @deco("123") def foo(data): "this is foo" print("foo") print(data) foo("afdasdf") print(foo.__name__, foo.__doc__)在decorator函数内部还需要定义一个函数来接受被装饰函数的参数。此时foo = deco(argv)(foo)(data)
首先注意第5行,如果注释这一行,foo.__name__将是'wrapper',使用这个装饰器后装饰之后的foo.__name__将还是原来的foo,wraps装饰器是将func的常用属性赋给了wrapper函数。另外相信你也注意到了,这个装饰器竟然带有一个参数。实际上,他还有另外两个可选的参数,assigned中的属性名将使用赋值的方式替换,而updated中的属性名将使用update的方式合并,你可以通过查看functools的源代码获得它们的默认值。对于这个装饰器,相当于wrapper = functools.wraps(func)(wrapper)
(3)多装饰器组合使用
def deco_1(func): def __deco_1(*args, **kwargs): print("__deco_1") return func(*args, **kwargs) print("deco_1") return __deco_1 def deco_2(func): def __deco_2(*args, **kwargs): print("__deco_2") return func(*args, **kwargs) print("deco_2") return __deco_2 @deco_2 @deco_1 def test(): print("test") # test = deco_1(test) # print(test) # test = deco_2(test) # print(test) test()
输出内容如下:
deco_1
deco_2
__deco_2
__deco_1
test
在申明函数test时就会输出前两行,真正执行的时候才会输出后三行。两个装饰器与注释部分的作用一样,可以替换。
(4)例子
def makebold(func): def wrapped(): return "<b>" + func() + "</b>" return wrapped def makeitalic(func): def wrapped(): return "<i>" + func() + "</i>" return wrapped @makebold @makeitalic def say(): return "Hello" print(say())
#output ''' <b><i>Hello</i></b> '''
3. 体会
当多个函数有重复代码时可以将此部分代码单独拿出来整理成一个装饰器,然后对每个函数调用该装饰器,这样可以实现代码的复用,而且可以让原来的函数更轻便。
还有就是当我们需要为多个已经写好的函数添加一个共同功能,比如检查参数的合法性。我们就可以单独写一个检查合法性的装饰器,然后在每个需要检查参数合法性的函数出调用即可,而不用去每个函数内部修改。
参考: