装饰器
python中装饰器做的事情:它们封装一个函数,并且用这样或者那样的方式来修改它的行为,装饰器让你在一个函数的前后去执行代码。在代码运行期间动态增加功能。
本质上:decorator(装饰器)就是一个返回函数的高阶函数
一、装饰器基础:
一切皆可对象,函数也是个对象,函数对象被赋值给变量,也可从函数中返回函数,甚至将函数作为参数传给另一个函数:
def now():
print('2020.2.10')
f=now # 一切皆可对象,函数也是个对象,函数对象被赋值给变量
f() # 调用该函数
利用__name__属性,可以拿到函数的名字:
print(now.__name__)
print(f.__name__) # __name__属性,拿到函数的名字
now
二、装饰器的蓝本:
from functools import wraps # 装饰器会重写被装饰函数的名字和注释文档(docstring),有些依赖函数签名的代码执行就会出错,当用__name__属性查看函数名字会变成装饰器中的函数名。可用functools.wraps解决这一问题
def decorator_name(f): # 装饰器,它的参数为function即函数
@wraps(f) # 用@来包裹被修饰的函数
def decorated(*args, **kwargs): # 参数定义是(*args, **kw),因此,decorated()函数可以接受任意参数的调用
if not can_run:
return "Function will not run" # return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回 None。
return f(*args, **kwargs) # 返回调用被修饰函数
return decorated
@decorator_name # 借助Python的@语法,把decorator置于函数的定义处
def func(): # 此处func()为被修饰函数
return("Function is running")
can_run = True
print(func())
# Output: Function is running
can_run = False
print(func())
# Output: Function will not run
print(func.__name__)
# Output:func __name__属性,拿到函数的名字.由于functools.wraps的帮助名字无问题
如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数:
import functools # 导入functools模块
def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
@log('execute')
def now():
print('2020-2-10')
>>> now()
execute now():
2020-2-10
三、类装饰器:
相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点
使用类装饰器主要依靠类的__call__
方法,当使用@
形式将装饰器附加到函数上时,就会调用此方法:
from functools import wraps
class logit(object):
def __init__(self, logfile='out.log'):
self.logfile = logfile
def __call__(self, func):
@wraps(func) # 语法糖@
def wrapped_function(*args, **kwargs):
log_string = func.__name__ + " was called"
print(log_string)
# 打开logfile并写入
with open(self.logfile, 'a') as opened_file:
# 现在将日志打到指定的文件
opened_file.write(log_string + '\n')
# 现在,发送一个通知
self.notify()
return func(*args, **kwargs)
return wrapped_function
def notify(self):
# logit只打日志,不做别的
pass
@logit() # 语法糖@
def myfunc1():
pass
四、装饰器顺序:
一个函数还可以同时定义多个装饰器,比如:
@a
@b
@c
def f ():
pass
它的执行顺序是从里到外,最先调用最里层的装饰器,最后调用最外层的装饰器,它等效于:
f = a(b(c(f)))
五、Task1:
设计一个decorator, 可作用于任何函数, 并打印该函数执行时间:
from functools import wraps
import time
# 设计一个decorator, 可作用于任何函数, 并打印该函数执行时间
def metric(func):
@wraps(func)
def wrapper(*args,**kw):
start=time.time()
func(*args,**kw)
end=time.time()
print('%s executed in %.4f ms'%(func.__name__,end-start)) # %.f表示浮点型输出,%.af表示输出保留小数点后a位并且考虑四舍五入,(如%.2f保留后2位,%.f保留0位(i.e不保留))
return func(*args,**kw)
return wrapper
# 测试
@metric
def fast(x, y):
time.sleep(0.0012)
return x + y;
@metric
def slow(x, y, z):
time.sleep(0.1234)
return x * y * z;
f = fast(11, 22)
s = slow(11, 22, 33)
if f != 33:
print('测试失败!')
elif s != 7986:
print('测试失败!')
六、Task2:
设计一个decorator,能在函数调用的前后打印出’begin call’和’end call’的日志,同时支持带参和不带参的函数运行:
from functools import wraps
def log(func1): # 此为高阶函数
if isinstance(func1,str): # 若高阶函数传入的参数是字符串,则可用isinstance判断
def decorator(func2): # 声明一个装饰器函数
@wraps(func2)
def wrapper(*args,**kw):
print('%s begin call %s'%(func1,func2.__name__))
func2(*args,**kw)
print('%s end call %s'%(func1,func2.__name__))
return func2(*args,**kw)
return wrapper
return decorator # 第一种情况,这是一个返回decorator(装饰器)的高阶函数
else:
@wraps(func1)
def wrapper(*args,**kw): # 第二种情况,无需声明再装饰器函数,该高阶函数即为装饰器函数
print('begin call %s'%func1.__name__)
func1(*args,**kw)
print('end call %s'%func1.__name__)
return func1(*args,**kw)
return wrapper
@log
def f():
pass
f()
# Output:
begin call f
end call f
@log('execute') # 先给log函数传入字符串类型的参数'execute',再给它传入函数类型的参数f()
def x():
pass
x()
# Output:
execute begin call x
execute end call x