“”"
装饰器目的是在不改变待装饰函数代码的情况下,增加额外的
功能,装饰器的返回值是已装饰的函数对象
“”"
假设有代码:
import time
def test():
time.sleep(2)
print("test is running!")
test()
很显然,这段代码运行的结果一定是:等待约2秒后,输出
“”"
高阶函数
那么对于高阶函数的形式可以有两种:
1.把一个函数名当作实参传给另外一个函数(“实参高阶函数”)
2.返回值中包含函数名(“返回值高阶函数”)
“”"
“”"
那么这里面所说的函数名,实际上就是函数的地址,也可以认为是函数的一个标签而已,
并不是调用,是个名词。如果可以把函数名当做实参,那么也就是说可以把函数传递到另一个函数,
然后在另一个函数里面做一些操作,根据这些分析来看,这岂不是满足了装饰器三原则中的第一条,
即不修改源代码而增加功能。那我们看来一下具体的做法:
“”"
import time
def test():
time.sleep(2)
print("test is running!")
def deco(func):
start = time.time()
func() # 2
stop = time.time()
print(stop - start)
deco(test) # 1
“”"
我们来看一下这段代码,在#1处,
我们把test当作实参传递给形参func,即func=test。
注意,这里传递的是地址,也就是此时func也指向了之前test所定义的那个函数体,
可以说在deco()内部,func就是test。在#2处,把函数名后面加上括号,就是对函数的调用(执行它)。
因此,这段代码运行结果是:
test is running!
the run time is 3.0009405612945557
“”"
“”"
我们看到似乎是达到了需求,即执行了源程序,
同时也附加了计时功能,但是这只满足了原则1(不能修改被装饰的函数的源代码),
但这修改了调用方式。假设不修改调用方式,那么在这样的程序中,
被装饰函数就无法传递到另一个装饰函数中去。
那么再思考,如果不修改调用方式,就是一定要有test()这条语句,那么就用到了第二种高阶函数,即返回值中包含函数名
“”"
import time
def test():
time.sleep(2)
print("test is running!")
def deco(func):
print(func)
return func
t = deco(test) # 3
t()#4
test()
“”“进化版”""
import time
def timer(func):# 5
def deco():
start = time.time()
func()
stop = time.time()
print(stop - start)
return deco
test = timer(test) # 6
def test():
time.sleep(2)
print("test is running!")
test() # 7
“”"
这段代码可能会有些困惑,怎么忽然多了这么多,暂且先接受它,分析一下再来说为什么是这样。
首先,在#6处,把test作为参数传递给了timer(),此时,在timer()内部,func = test,接下来,
定义了一个deco()函数,当并未调用,只是在内存中保存了,并且标签为deco。在timer()函数的最后返回deco()的地址deco。
然后再把deco赋值给了test,那么此时test已经不是原来的test了,也就是test原来的那些函数体的标签换掉了,换成了deco。
那么在#7处调用的实际上是deco()。
那么这段代码在本质上是修改了调用函数,但在表面上并未修改调用方式,而且实现了附加功能。
那么通俗一点的理解就是:
把函数看成是盒子,test是小盒子,deco是中盒子,timer是大盒子。
程序中,把小盒子test传递到大盒子temer中的中盒子deco,然后再把中盒子deco拿出来,打开看看(调用)
“”"
“”"
3、 真正的装饰器
根据以上分析,装饰器在装饰时,需要在每个函数前面加上:
test = timer(test)
显然有些麻烦,Python提供了一种语法糖,即:
@timer
“”"
“”“实例”""
# def dec(f):
# n = 3
# def wrapper(*args, **kw):
# return f(*args, **kw) * n
# return wrapper
#
#
# @dec
# def foo(n):
# return n * 2
#
# print(foo(2))
# 装饰器有参数
# import time
#
# def timer(func):
# def deco():
# start = time.time()
# func()
# stop = time.time()
# print(stop-start)
# return deco
#
# @timer
# def test(parameter): #8
# time.sleep(2)
# print("test is running!")
# test()
“”"
对于一个实际问题,往往是有参数的,如果要在#8处,给被修饰函数加上参数,显然这段程序会报错的。
错误原因是test()在调用的时候缺少了一个位置参数的。而我们知道test = func = deco,因此test()=func()=deco()
,那么当test(parameter)有参数时,就必须给func()和deco()也加上参数,
为了使程序更加有扩展性,因此在装饰器中的deco()和func(),加如了可变参数*agrs和 **kwargs。
“”"
import time
def timer(func):
def deco(*args, **kwargs):
start = time.time()
func(*args, **kwargs)
stop = time.time()
print(stop-start)
return deco
@timer
def test(parameter): #8
time.sleep(2)
print("test is running!")
test()
“”"
带返回值
“”"
import time
def timer(func):
def deco(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
stop = time.time()
print(stop-start)
return res
return deco
@timer
def test(parameter): #8
time.sleep(2)
print("test is running!")
return "Returned value"
test()
总结:
1, 把函数看成是盒子,test是小盒子,deco是中盒子,timer是大盒子。
程序中,把小盒子test传递到大盒子temer中的中盒子deco,然后再把中盒子deco拿出来,打开看看(调用)‘
2, 根据以上分析,装饰器在装饰时,需要在每个函数前面加上:
test = timer(test)
显然有些麻烦,Python提供了一种语法糖,即:
@timer’