装饰器使用的是函数中的闭包功能
一、装饰器
装饰器: 在函数名以及函数体不改变的前提下, 给一个函数附加一些额外代码
二、语法
- 语法
@装饰器
def 被装饰的函数():
code
三、举例
def name(func):
def inner():
print("zhangsan")
func()
return inner
@name
def say():
print("hello world")
say()
# 打印结果:
zhangsan
hellow world
四、讲解
- say函数定义上添加
@name
等价于say = name(say)
@name
def say():
print("hello world")
- 等价于:
def say():
print("hello world")
say = name(say)
五、注意: 装饰器的执行时间是立即实行
- 当代码执行到@name时, 实际上函数
name
已经被执行了, 并且传入的参数func
的值是say
, 执行后say
指向新的函数, 此时变成下面的代码:
def inner()
print("zhangsan")
say()
- 结果:
say
没有被调用, 依然打印xxxxxxx
def name(func):
print("xxxxxxx")
def inner():
print("zhangsan")
func()
return inner
@name
def say():
print("hello world")
# 打印结果:
xxxxxxx
六、案例: 添加装饰器的过程
现有模拟案例如下:
-
已知有两个功能函数, 分别是发图片和发说说
def ftp(): print("发图片") def fss(): print("发说说")
-
在页面上有两个按钮, 点击第一个按钮时发出片, 点击第二个按钮时发说说
btnIndex = 1 if btnIndex == 1: ftp() else: fss()
注意: 此处是模拟, 当btnIndex的值为1时, 认为点击第一个按钮, 值为2时, 认为点击第二个按钮
-
由于发图片和发说说都是用户登陆后才能操作, 所以在发图片和发说说前, 都需要进行登录验证
btnIndex = 1 if btnIndex == 1: print("登录验证") ftp() else: print("登录验证") fss()
注意: 此处使用
print("登录验证")
表示了登录验证的功能, 真实项目中替换为具体的代码实现即可 -
上面已经解决了在发图片和发说说前的登录验证功能
但是却有一个问题, 那就是发图片和发说说这两个功能, 也可以在当前项目中其他的地方使用
而那时又需要做登录验证操作
这样会造成代码冗余, 且不利于维护, 不利于阅读
所以我们将登录验证功能换一个地方, 而逻辑代码不变, 如下
def ftp(): print("登录验证") print("发图片") def fss(): print("登录验证") print("发说说") btnIndex = 1 if btnIndex == 1: ftp() else: fss()
-
第4步中解决了在使用发图片和发说说两个功能时, 只需要直接调用函数即可, 函数内部已经实现了对登录的验证操作, 多个地方使用这两个功能也只需要直接调用即可
但是, 这里有一个问题, 我们的模拟代码
print("登录验证")
只有一句话, 而在实际操作过程中, 一个登录验证可能会有很多行的代码, 而发说说和发图片中都使用了这段代码, 此时会造成代码冗余所以, 在第四步的基础上, 将登录验证的代码抽取出来, 此时有如下代码
def checkLogin(): print("登录验证") def ftp(): checkLogin() print("发图片") def fss(): checkLogin() print("发说说")
我们只需要在发图片和发说说的具体实现前, 加入登录验证函数的调用即可
-
在第5步中, 我们解决了登录验证代码过多, 可能导致第四步中, 发图片函数和发说说函数中登录验证代码过多的冗余问题
看起来问题解决了, 但是却违背了"开放封闭"原则:
1, 已经写好的代码, 尽可能不要修改 2, 如果想要新增功能, 在原先代码基础上, 单独进行扩展
并且违背了功能模块, 功能单一职责
所以我们需要将发图片和发说说两个函数中的
checkLogin()
, 去掉, 并实现下面的函数def ftp(): print("发图片") def fss(): print("发说说") def checkLoginToFtp(): print("登录验证") ftp() def checkLoginToFss(): print("登录验证") fss()
在业务逻辑中, 当按钮被点击时的调用如下
btnIndex = 1 if btnIndex == 1: checkLoginToFtp() else: checkLoginToFss()
这样, 我们就在不改变
ftp()
和fss()
两个函数的基础上, 添加了登录验证功能 -
第6步中的代码会出现冗余现象, 比如
checkLoginToFtp
和checkLoginToFss
都调用了print("登录验证")
这里对第6步中的代码进行简化
def ftp(): print("发图片") def fss(): print("发说说") def checkLogin(func): print("登录验证") func()
调用时
btnIndex = 1 if btnIndex == 1: checkLogin(ftp) else: checkLogin(fss)
此时, 可以直接使用
checkLogin
函数, 对在指定函数前添加登录验证了 -
第7步中, 对代码进行了优化, 可以自由的在任何函数前都添加登录验证操作, 且不会出现代码冗余的情况
但是, 这里修改了第2步中, 逻辑代码部分的实现内容
现在要做的是, 在第1步和第2步中的代码都不变的基础上, 通过添加额外的代码, 实现登录验证的操作
已知: 两个功能函数
ftp
和fss
, 而这两个函数的函数名, 实质上就是变量, 那么我们通过将函数名重新赋值的方式, 修改函数指向def checkLogin(func): print("登录验证") func() ftp = checkLogin(ftp) fss = checkLogin(fss)
此时,
ftp
和fss
的值已经不是之前的函数, 而是checkLogin(ftp)
和checkLogin(fss)
的返回值, 即 None -
在第8步中, 我们修改了变量
ftp
和fss
的指向, 此时两个变量的值都是 None我们知道, 第2步中的
ftp
和fss
是两个不同的函数, 所以才能被调用, 而此时的ftp
和fss
已经无法像函数一样被调用我们需要修改
checkLogin
函数, 让他的返回值依然是函数def checkLogin(func): def inner(): print("登录验证") func() return inner
这样, 我们在给
ftp
和fss
赋值ftp = checkLogin(ftp) fss = checkLogin(fss)
此时,
ftp
的值是函数def inner(): print("登录验证") ftp()
fss
的值是函数def inner(): print("登录验证") fss()
此时
ftp
和fss
可以如下调用btnIndex = 1 if btnIndex == 1: ftp() else: fss()
-
我们看看现在的代码
def checkLogin(func): def inner(): print("登录验证") func() return inner def ftp(): print("发图片") def fss(): print("发说说") ftp = checkLogin(ftp) fss = checkLogin(fss) btnIndex = 1 if btnIndex == 1: ftp() else: fss()
此时, 通过加入的代码, 已经将
ftp
和fss
两个函数指向了新的函数而这个新函数在实现原有代码之前, 加入了登录验证功能
此时: 我们实现了, 在1和2代码不变的情况下, 加入新代码, 增加了新的功能
-
在上述代码中, 有
ftp = checkLogin(ftp)
和fss = checkLogin(fss)
两个重新赋值的操作, 在Python中, 有一个简单写法, 与这两句等价def checkLogin(func): def inner(): print("登录验证") func() return inner @checkLogin def ftp(): print("发图片") @checkLogin def fss(): print("发说说")
这种简易方法, 就是语法糖, 即能用简单的写法, 写出复杂的功能, 如同糖一样甜
上面的代码中
ftp函数
上添加@checkLogin
等价于ftp = checkLogin(ftp)
-
第11步中的
checkLogin函数实现
和@checkLogin
, 就是装饰器装饰器: 在函数名以及函数体不改变的前提下, 给一个函数附加一些额外代码
总结: 通过闭包的形式, 将原有函数名重新赋值一个新的函数, 这个新函数中调用了原有函数, 并在原有函数的基础上添加了新的内容, 当通过该函数名调用时, 会执行更多的功能, 这就是装饰器的作用
七、进阶
1、装饰器叠加
def printStar(func):
print("1111")
def inner():
print("*" * 30)
func()
return inner
def printLine(func):
print("2222")
def inner():
print("-" * 30)
func()
return inner
@printStar
@printLine
def name():
print("name")
# 打印结果
2222
1111
- 可以得出, 当多个装饰器同时修饰同一个函数式, 距离函数最新的装饰器最先被加载
name = printLine(name)
name = printStar(name)
- 此时我们直接调用
name
函数
def printStar(func):
def inner():
print("*" * 30)
func()
return inner
def printLine(func):
def inner():
print("-" * 30)
func()
return inner
@printStar
@printLine
def name():
print("name")
name() # 调用
# 打印结果:
******************************
------------------------------
name
- 由上面的打印可以知道, 距离函数最远的装饰器, 在函数被调用时, 最先被执行
2、对有参函数进行装饰
- 装饰目标:
def sum(num1, num2)
print(num1, num2)
- 装饰器
def decorator(func):
def inner(*args, **kwargs):
func(*args, **kwargs)
return inner
- 结果:
def decorator(func):
def inner(*args, **kwargs):
print("-" * 20)
func(*args, **kwargs)
return inner
@decorator
def sum(num1, num2):
print(num1, num2)
sum(1, 2)
# 打印结果:
--------------------
3
3、对有返回值的函数进行装饰, 无论什么场景, 保持函数返回值一致
- 装饰目标:
def sum(num1, num2):
return num1 + num2
- 装饰器
def decorator(func):
def inner(*args, **kwargs):
result = func(*args, **kwargs)
return result
return inner
- 结果
def decorator(func):
def inner(*args, **kwargs):
print("-" * 20)
result = func(*args, **kwargs)
return result
return inner
@decorator
def sum(num1, num2):
return num1 + num2
print(sum(num1 = 1, num2 = 2))
# 打印结果:
--------------------
3
4. 带有参数的装饰器
- 装饰目标
def name1():
print("zhangsan")
def name2():
print("lisi")
-
目标: 在打印
zhangsan
之前, 先打印20个*
, 在打印lisi
之前, 先打印20个-
-
装饰器
def symbol(char):
def decorator(func):
def inner():
print(char * 20)
func()
return inner
return decorator
- 结果:
def symbol(char):
def decorator(func):
def inner():
print(char * 20)
func()
return inner
return decorator
@symbol("*")
def name1():
print("zhangsan")
@symbol("-")
def name2():
print("lisi")
name1()
name2()
# 打印结果:
********************
zhangsan
--------------------
lisi
通过@装饰器(参数)的方式, 调用这个函数, 并传递参数, 并把返回值, 再次当做装饰器进行使用
先计算@后面的内容, 并把这个内容当做是装饰器
注意: 装饰器@xxxx
, 后面的xxxx
的结果必须是一个函数, 并且这个函数只能拥有一个参数, 用来接收函数名
执行@symbol("*")
时, 会先执行symbol("*")
, 然后将执行结果作为装饰器, 即decorator
网友评论