装饰器是给现有的模块增添新的小功能,可以对原函数进行功能扩展,而且还不需要修改原函数的内容,也不需要修改原函数的调用。
以下的log函数就是一个装饰器:
import functools
def log(text): # ☀
def decorator(func): # ☀
@functools.wraps(func) # 这里是把原始函数的__name__等属性复制到wrapper()函数中
def wrapper(*args, **kw): # ☀
print('%s():%s' % (func.__name__,text))
func(*args, **kw)
print('%s():%s' % (func.__name__,'结束运行'))
return wrapper
return decorator
@log('进行加法计算') # 这是装饰器的语法糖,相当于这样写: count=log('进行加法计算')(count)
def count(num1,num2):
print('{}+{}={}'.format(num1,num2,num1+num2))
count(1,7)
注意看三处“☀”,一共有三层函数相嵌套,每一层我们都传入了一些参数:最外层 log() 传入了装饰器的参数 text;第二层 decorator() 传入了一个函数,这个函数就是被装饰的函数;第三层 wrapper() 传入了被装饰的函数可能需要的参数。如此,这些参数都可以直接在 wrapper() 里调用。
在 wrapper() 里,我们在调用原函数的前后打印了一些文字。最后的输出结果:
count():进行加法计算
1+7=8
count():结束运行
[Finished in 1.0s]
上面是基于函数实现的装饰器,在阅读别人代码时,还可以时常发现还有基于类实现的装饰器。上面的代码还可以这样改写:
class Log():
def __init__(self,text): # ☀
self.text = text
def __call__(self,func): # ☀
def wrapper(*args, **kw): # ☀
print('%s():%s' % (func.__name__,self.text))
func(*args, **kw)
print('%s():%s' % (func.__name__,'结束运行'))
return wrapper
@Log('进行加法计算')
def count(num1,num2):
print('{}+{}={}'.format(num1,num2,num1+num2))
count(1,7)
显然,Log就是我们的类装饰器。类装饰器要用到两个魔法方法:__init__和__call__。
传入参数的方法换汤不换药,还是上面提到的三层。注意三个“☀”,第一个“☀”在__init__处传入了装饰器的参数 text ,第二个“☀”在__call__处传入了被装饰的函数,然后我们在__call__内部定义了一个函数 wrapper(),第三个“☀”在这里传入被装饰的函数可能需要的参数。
最后的效果一样。
如果装饰器不需要参数 text,则只用传入两层参数,上述两种装饰器分别可这样改写:
# 两层参数分别落脚__init__和__call__,不必在__call__里再定义一个函数
class Log():
def __init__(self,func): # ☀
self.func = func
def __call__(self,*args,**kw): # ☀
print('%s():%s' % (self.func.__name__,'开始运行'))
self.func(*args,**kw)
print('%s():%s' % (self.func.__name__,'结束运行'))
@Log
def count(num1,num2):
print('{}+{}={}'.format(num1,num2,num1+num2))
count(1,7)
import functools
#将最外一层的log函数剥掉
def decorator(func): # ☀
@functools.wraps(func)
def wrapper(*args, **kw): # ☀
print('%s():%s' % (func.__name__,'开始运行'))
func(*args, **kw)
print('%s():%s' % (func.__name__,'结束运行'))
return wrapper
@decorator # 装饰器的语法糖,相当于: count=decorator(count)
def count(num1,num2):
print('{}+{}={}'.format(num1,num2,num1+num2))
count(1,7)
原先 count() 的作用是打印一个算式,但如果我要它在打印完之后再返回一个值呢?——
.....
@decorator # 相当于: count=decorator(count)
def count(num1,num2):
print('{}+{}={}'.format(num1,num2,num1+num2))
return num1+num2
num = count(1,7)
print(num)
此时num的值是多少?是8吗?最后我们得到一个出乎意料的结果:None。为什么会这样呢?
由于我们对 count 使用了装饰器,调用 count 相当于在调用 decorator(count),而decorator(count)又会返回一个 wrapper 。因为我们没有给wrapper函数设置返回值,所以默认返回None。
为解决这个问题,我们应该把wrapper函数改成这样:
.....
def wrapper(*args, **kw):
print('%s():%s' % (func.__name__,'开始运行'))
f = func(*args, **kw)
print('%s():%s' % (func.__name__,'结束运行'))
return f
.....
这样一来,既没有影响装饰器的功能,又返回了原函数想返回的值。
参考资料:
装饰器 - 廖雪峰的官方网站 (liaoxuefeng.com)【Python】一文弄懂python装饰器(附源码例子)_攻城狮白玉的博客-CSDN博客