装饰器的功能在很多语言中都有,名字也不尽相同,其实它体现的是一种设计模式,强调的是开放封闭原则,更多的用于后期功能升级而不是编写新的代码。装饰器不光能装饰函数,也能装饰其他的对象,比如类。
开放封闭原则:规定已经实现的功能代码内部不允许被修改,但外部可以被扩展,即,封闭:已实现的功能代码块;开放:对扩展开放。
语法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
def wrapper(func): #func接收被装饰函数的函数名‘foo’ def result(): print ( 'before' ) #增加被装饰函数执行前的功能 res = func() #执行被装饰函数‘foo()’ print ( 'after' ) #增加被装饰函数执行后的功能 return res return result @wrapper #将扩展的功能使用‘@’语法糖写在被装饰函数的上方 def foo(): print ( 'foo' ) foo() |
上述实例解读:
1. 程序开始运行,从上往下编译,读到def wrapper(func):的时候,发现这是个“一等公民”->函数,于是把函数体加载到内存里,然后过。
2. 读到@wrapper的时候,程序被@这个语法糖吸引住了,知道这是个装饰器,按规矩要立即执行的,于是程序开始运行@后面那个名字wrapper所定义的函数。(@wrapper只能放在被装饰的函数的上方最近处,不要空行。)
3. 程序返回到wrapperr函数,开始执行装饰器的语法规则,这部分规则是定死的。规则是:被装饰的函数的名字会被当作参数传递给装饰函数。装饰函数执行它自己内部的代码后,会将它的返回值赋值给被装饰的函数。
- @wrapper和@wrapper()有区别,没有括号时,wrapper函数依然会被执行,这和传统的用括号才能调用函数不同,需要特别注意!有括号时,就可以给装饰器传递参数
- 是foo这个函数名(而不是函数foo()的返回值)当做参数传递给装饰函数wrapper,也就是:func = foo,@wrapper等于wrapper(foo),实际上传递了foo的函数体,而不是执行foo后的返回值。
- wrapper函数return的是result这个函数名,而不是result()这样被调用后的返回值。
4. 程序开始执行wrapper函数内部的内容,一开始它又碰到了一个函数,result函数定义块被程序观察到后不会立刻执行,而是读入内存中(这是潜规则)。
5. 再往下,碰到return res,返回值是个函数名,并且这个函数名会被赋值给foo这个被装饰的函数,也就是foo = res。
6. 至此,当调用foo函数时,首先执行的时result函数的代码,在本例中,首先打印‘before’,然后执行func,也就是被装饰函数foo,并将返回值赋给res变量,然后继续执行result函数,打印‘after’。最后返回res。
以上流程走完,既没有修改foo程序代码,也没有更改其调用方式,就实现了在执行foo前后增加功能的需求。
疑问:为什么要定义2个函数(wrapper和result),一层函数不行吗?
如果只有一层函数,执行到@wrapper时,会自动执行wrapper内部的代码,如果不封装一下,在foo函数未调用时就执行了foo,这与需求不符。
装饰器的参数传递:
被装饰函数有一个参数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
def wrapper(func): #func接收被装饰函数的函数名‘foo’ def result(name): print ( 'before' ) #增加被装饰函数执行前的功能 res = func() #执行被装饰函数‘foo()’ print ( 'after' ) #增加被装饰函数执行后的功能 return res return result @wrapper #将扩展的功能使用‘@’语法糖写在被装饰函数的上方 def foo(name): print (name) foo( 'jack' ) |
被装饰函数有多个参数:
一个函数被多个函数装饰:
装饰器实例
程序需求:
- 在不改变func_1函数(程序)定义和调用方式的基础上,添加计时和用户认证功能
- 用户认证要求:
- 用户最多尝试3次登陆
- 当存在的用户登陆失败3次后,锁定该用户,限制登陆
程序代码:
验证过程:
1. account文件:存放用户登陆数据
2. locked_list文件:存放被锁定用户名
当前为空
3. 尝试登陆正确的用户
4. 尝试不同用户登陆失败
5. 尝试同一用户登陆失败
6. 尝试同一用户登陆失败,但该用户本身不存在
参考资料: