1. 什么是装饰器?
- 装饰器是给现有的模块增添新的小功能,可以对原函数进行功能扩展,而且还不需要修改原函数的内容,也不需要修改原函数的调用。
1.1 装饰器的使用符合了面向对象编程的开放封闭原则。
开放封闭原则主要体现在两个方面:
-
对扩展开放
,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
-
对修改封闭
,意味着类一旦设计完成,就可以独立其工作,而不要对类尽任何修改。
2. 引入
实现打印功能,发说说, 发图片, 但必须有一个前提, 就是,用户必须登录之后再进行说说图片的发送
# 定义两个功能函数
def fss():
checkLogin()
print("发说说")
def ftp():
checkLogin()
print("发图片")
# 登录验证的操作
def checkLogin():
print("登录验证...")
# 相关的逻辑代码
btnIndex = 2
if btnIndex == 1:
fss()
else:
ftp()
执行结果:
-
直接在业务逻辑代码里面去修改, 添加一个验证操作
因为业务逻辑代码非常多, 所以, 就造成了, 每一份, 逻辑代码, 在调用, 具体的功能函数之前, 都需要, 去做一个登录验证, 代码冗余度, 就比较大, 代码的复用性比较差, 代码的维护性比较差
- 直接在功能函数里面, 去修改, 方便代码的重用
3. 装饰器
@
:装饰器的语法糖
def checkLogin(func):
def inner():
print("登录验证...")
func()
return inner
# 加入装饰器后,自动执行了 fss = checkLogin(fss)
@checkLogin
def fss():
print("发说说")
# 语法糖 写法,自动执行 ftp = checkLogin(ftp)
@checkLogin
def ftp():
print("发图片")
# 相关的逻辑代码
btnIndex = 1
if btnIndex == 1:
fss()
else:
ftp()
执行结果:
可以看到,装饰器其实是一个闭包函数,当用 checkLogin
装饰 fss
时,会自动执行 fss = checkLogin(fss)
, 从而使得函数整体的功能发生了改变,封装到了闭包中,当再此调用 fss()
时,就会先输出登录验证…
在下述两种条件下,就需要使用装饰器来实现
- 函数名字不能发生改变
- 函数体内部的代码不能发生改变
def check(func):
print("xxx")
def inner():
print("登录验证操作....")
func()
return inner
@check
def fss():
print("发说说")
执行结果:
这里我们并没有显式的调用函数,却输出了 xxx
,这也充分说明了,我们会先执行 fss = check(fss)
从而打印出了在外层的 xxx
4. 装饰器叠加
多个装饰器叠加时: 从内到外装饰、从外到内执行
从内到外装饰,即先利用 zhuangshiqi_star
装饰 print_content()
函数,再利用 zhuangshiqi_line
将之前看做一个整体进行装饰,在执行的时候,先执行最外层封装的,也就是 zhuangshiqi_line
然后再执行 zhuangshiqi_star
最后执行 print_content()
def zhuangshiqi_line(func):
def inner():
print("---------------------------")
func()
return inner
def zhuangshiqi_star(func):
def inner():
print("*" * 30)
func()
return inner
@zhuangshiqi_line # print_content = zhuangshiqi_line(print_content) 再装饰这个
@zhuangshiqi_star # print_content = zhuangshiqi_star(print_content) 先装饰这个
def print_content():
print("hello, world !")
print_content()
执行结果:
5. 对有参函数进行装饰器
在用 zsq
装饰 pnum
函数时,会首先自动执行 pnum = zsq(pnum)
,此时会将参数传递到 zsq
的 inner
中,这里 inner
应该设置变长参数,*args, **kwargs
,来接收参数,并在内部,执行 func(*args, **kwargs)
从而保持一致
def zsq(func):
# *args, **kawargs 用于接收不定长参数
def inner(*args, **kwargs):
print("_" * 30)
# print(args, kwargs)
func(*args, **kwargs) # 注意这里由于在本身函数调用时需要出传入参数执行
return inner
@zsq # 装饰带多个参数的函数
def pnum(num, num2, num3):
print(num, num2, num3)
@zsq # 装饰带一个参数的函数
def pnum2(num):
print(num)
pnum(123, 222, num3=666)
pnum2(999)
执行结果:
6. 带返回值的装饰器
在用 zsq
装饰 pnum
函数时,会首先自动执行 pnum = zsq(pnum)
,此时会将参数传递到 zsq
的 inner
中,这里 inner
应该设置变长参数,*args, **kwargs
,来接收,并且,由于需要返回值,在 inner
中执行 func
函数时,需要接收返回值,并再一次返回,也就是 return res
部分
def zsq(func):
def inner(*args, **kwargs):
print("_" * 30)
# print(args, kwargs)
res = func(*args, **kwargs)
return res # 这里可以合并上下两行,直接写为 return func(*args, **kwargs)
return inner
@zsq # pnum = zsq(pnum)
def pnum(num, num2, num3):
print(num, num2, num3)
return num + num2 + num3
@zsq
def pnum2(num):
print(num)
res1 = pnum(123, 222, num3=666)
res2 = pnum2(999)
# 打印返回的参数
print(res1, res2)
执行结果:
7. 带参数的装饰器
先定义一个 getzsq
函数,函数的作用就是返回一个装饰器,返回的装饰器再接收一个 func 函数,装饰时自动执行 f1 = zsq(f1)
,内部包含一个闭包将其封装起来,这里闭包是固定格式,不能把参数加到闭包函数内,所有只能加在最外层,并返回一个装饰器,根据不同的参数,生成不同的装饰器返回
执行过程,先执行 getzsq(*)
, 返回的装饰器再执行 f1 = zsq(f1),进行装饰,此时的 char 值被封装到了 装饰器内部
def getzsq(char):
def zsq(func):
def inner():
print(char * 30)
func()
return inner
return zsq
@getzsq("*") # f1 = zsq(f1)
def f1():
print("666")
f1()
执行结果:
上面例子中的装饰器是不是功能太简单了,那么装饰器可以加一些参数吗,当然是可以的,另外装饰的函数当然也是可以传参数的。
def logging(level):
def outwrapper(func):
def wrapper(*args, **kwargs):
print("[{0}]: enter {1}()".format(level, func.__name__))
return func(*args, **kwargs)
return wrapper
return outwrapper
@logging(level="INFO")
def hello(a, b, c):
print(a, b, c)
hello("hello,","good","morning")
执行结果:
8. 类装饰器
类装饰器的实现是调用了类里面的__call__函数。类装饰器的写法比我们装饰器函数的写法更加简单。
类装饰器实现流程:
- 通过
__init__()
方法初始化类
- 通过
__call__()
方法调用真正的装饰方法
class logging(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print("[DEBUG]: enter {}()".format(self.func.__name__))
return self.func(*args, **kwargs)
@logging # 实际是执行 hello = logging(hello) 所以需要 __init__ 方法,并接收一个 func 参数
def hello(a, b, c):
print(a, b, c)
# 经过 hello = logging(hello) 后返回的是一个实例方法,不能被直接调用,所以需要实现 __call__ 方法
hello("hello,","good","morning")
执行结果:
9. 带参数的类装饰器
如下实现:
class logging(object):
def __init__(self, level):
self.level = level
def __call__(self, func):
def wrapper(*args, **kwargs):
print("[{0}]: enter {1}()".format(self.level, func.__name__))
return func(*args, **kwargs)
return wrapper
@logging(level="TEST")
def hello(a, b, c):
print(a, b, c)
hello("hello,","good","morning")
执行结果:
Reference
1. 【Python】一文弄懂python装饰器(附源码例子)