1.装饰器基本概念
装饰器就是给现有的函数添加功能,并且不改变它原有的调用方式,本质上就是一个高阶函数(参数是函数名并且返回函数名),在python中,万物皆对象,函数名也是一个对象可以被作为参数传递,这和C语言中的回调函数很相似。
2.装饰器
2.1 装饰器没有参数并且被装饰函数也没有参数
我们想要设计一个函数,能统计任何函数的运行时间,该怎么设计呢?
from time import time, sleep
def func1():
sleep(3)
print('in func1')
def get_run_time(parameter_func):
start_time = time()
parameter_func()
end_time = time()
print('运行时间为:', end_time - start_time)
get_run_time(func1)
这段代码中,get_run_time函数接收函数名作为参数,在函数体中记录时间并且调用对应的函数达到记录函数运行时间的功能
但是上面的函数已经不通过func1调用了,而是使用了另一个函数,所以它不是装饰器,那要怎么才能让他变为一个装饰器呢?很简单,让原函数被赋值为get_run_time函数不就得了?那需要get_run_time返回值也是一个函数,但是get_run_time中没有函数,所以我们需要在get_run_time中再定义一个函数(函数的嵌套定义),看下面的代码:
我们看到,在没有改变原函数的调用方式的前提下,达到了给func1新增计时的功能,这就是一个最简单的装饰器函数,但是它怎么不是 @classmethod 这种形式呢?别急,它可以转换为这种形式,因为 @ 是一个语法糖,它的本质和我们上面写的函数类似,看下面的代码:
from time import time, sleep
def func1():
sleep(3)
print('in func1')
def get_run_time(parameter_func):
def temp():
start_time = time()
parameter_func()
end_time = time()
print('运行时间为:', end_time - start_time)
return temp
@get_run_time #实际上执行了 func2 = get_run_time(func2)
def func2():
sleep(5)
print('in func2')
func1 = get_run_time(func1)
func1()
func2()
我们使用@这个语法糖给func2这个函数增加了功能,现在就和@classmethod一样了,并且也能完成相应的功能,下面是运行结果。
2.2 被装饰函数有参数
from time import time, sleep
def get_run_time(parameter_func):
def temp():
start_time = time()
parameter_func()
end_time = time()
print('运行时间为:', end_time - start_time)
return temp
@get_run_time
def func1(name):
sleep(3)
print(f'我叫{name}','in func1')
func1('小明')
这段代码会报错,TypeError: get_run_time.<locals>.temp() takes 0 positional arguments but 1 was given,因为:
执行 @get_run_time装饰器相当于执行 func1=get_run_time(func1)。
get_run_time返回内部的temp函数,所以func1 = temp,因此 func1('小明') == temp('小明'),但是temp是不接收参数的,因此改正方法就很清晰了,修改定义的temp函数,让它接收参数
2.3 装饰器本身带参数
from time import time, sleep
def get_run_time(parameter_func , time_type):
print(time_type)
def temp(*args, **kwargs):
start_time = time()
parameter_func(*args, **kwargs)
end_time = time()
print('运行时间为:', end_time - start_time)
return temp
@get_run_time(time_type = 'seconds')
def func1(name,age):
sleep(3)
print(f'我叫{name},今年{age}岁','in func1')
func1('小明',20)
按照我们本来的想法,装饰器本身带参数,那么我在定义和调用时都给他一个参数不就完了吗?但是上面的代码会报错:TypeError: get_run_time() missing 1 required positional argument: 'parameter_func',因为parameter_func没有接收到值,所以我们需要修改装饰器
为什么需要嵌套定义outer和inner呢?我们可以姑且这么理解:最外层get_run_time()消化了time_type,outer()消化了func1这个函数名,inner消化了name和age这两个实参。
2.4 被装饰函数带返回值
经过上面的分析,我们已经很清楚了,func1最后是得到最内层的函数地址,所以我们在最内层中保存通过函数名调用的func1的返回值,在inner运行结束后返回即可:
from time import time, sleep
def get_run_time( time_type):
print(time_type)
def outer(parameter_func):
def inner(*args, **kwargs):
start_time = time()
ret = parameter_func(*args, **kwargs)
end_time = time()
print('运行时间为:', end_time - start_time)
return ret
return inner #outer返回inner的地址
return outer #get_run_time返回outer的地址
@get_run_time(time_type = 'seconds')
def func1(name,age):
sleep(3)
print(f'我叫{name},今年{age}岁','in func1')
return name
print(func1('小明', 20))
2.5 总体对比
#被装饰函数带不带参数均可
def decorator1(func):
def inner(*args, **kwargs):
func(*args, **kwargs)
return inner
#装饰器本身带参数
def decorator2(para):
"""
对参数para的操作
"""
def outer(func):
def inner(*args, **kwargs):
func(*args, **kwargs)
return inner
return outer
#被装饰函数带返回值
def decorator3(para):
"""
对参数para的操作
"""
def outer(func):
def inner(*args, **kwargs):
ret = func(*args, **kwargs)
return ret
return inner
return outer