装饰器
# 引入
我们循序渐进, 先来回顾一些知识.
在Python里,所有东西都是object,函数也不例外,也是Object(对象).
当我们在python中所谓定义一个函数的时候,我们只是新建了一个变量,
然后这个变量里面保存了一个函数对象,也就是 function object
!
注: function object有一个特点,它是一个 callable, callable可以在后面加一个小括号去调用它!
Ps: 每一个function object都会有它对应的一个code object.
当我们清楚在Python里,函数不过是一个普通对象. 你就非常容易理解,函数可以被当做参数传进其它函数里.
def double(x):
return 2 * x
def triple(x):
return 3 * x
def calc_number(func, x):
print(func(x))
if __name__ == '__main__':
calc_number(double, 2) # 4
calc_number(triple, 2) # 6
2
3
4
5
6
7
8
9
10
11
12
13
14
15
在上述程序里, 定义了double、triple, 这两个保存着函数对象的变量, 都可被传进 calc_number 这个函数里!
函数没有什么神奇的地方, 完全可以像其他变量一样被传来传去. 被传进其他函数.
那同样的, 函数不仅可以被作为变量传进其他函数, 函数本身也可以成为一个返回值!
我们可以将上述的程序进行优化
def get_multiple_func(n):
def multiple(x):
return n * x
return multiple
if __name__ == '__main__':
double = get_multiple_func(2)
triple = get_multiple_func(3)
print(double(2)) # 4
print(triple(2)) # 6
2
3
4
5
6
7
8
9
10
11
12
13
当你理解了, 函数可以作为参数被传进其他函数 以及 函数的返回值可以是一个函数.
那么decorator, 相对来说就变得比较容易理解啦.
说在前面: 看完整篇博文, 你就能理解这句话啦! ★ decorator本身就是一个callable. 它也没有什么特殊的地方!
# 函数装饰器
我们先写一个极简的decorator.
def dec(f):
pass
@dec
def my_func(x):
return 2 * x
2
3
4
5
6
7
当我们装饰一个函数时,会用到语法糖. 在上述程序中, 语法糖具体体现在@dec
. what is @dec
?
根据高天大佬对程序的字节码分析, 可知 @dec
def my_func(x):return 2*n
这一坨 完全等价于 my_func = dec(my_func)
通过这个等式, 我们可以得出结论:
-1- 应用语法糖后, 我们就可以将 dec函数 称作为decorator, @后面紧跟着的 dec 是一个函数名!
-2- decorator的输入一定是一个函数, decorator的输出通常也是函数, 但不一定就是函数.
举一个极端的例子来佐证上面的第二点结论.
def dec(f):
return 1
@dec
def my_func(x):
return 2 * x
if __name__ == '__main__':
print(my_func) # 1
2
3
4
5
6
7
8
9
10
11
我们的decorator return的是1, 在my_func函数上加上该decorator, 打印my_func, 结果也是1.
注意哦, 我们print的是 my_func这个变量本身, 并没有对其加括号进行调用哦!
为什么 my_func 变成了 1 呢? 回归等式 my_func = dec(my_func)
, 就很容易理解了!
尽管如此, 但在绝大多数场景下, 我们还是认为:
decorator是一个参数是函数, 返回值也是函数的函数! / decorator不过是一个输入和输出都是函数的函数.
(不严谨, 但现目前看来够用了!)
import time
def timeit(f):
def wrapper(x):
start = time.time()
ret = f(x)
print(time.time() - start)
return ret
return wrapper
@timeit
def my_func(x):
time.sleep(x)
@timeit
def other_func(x):
return x * 2
if __name__ == '__main__':
print(my_func(1))
print(other_func(3))
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
上述程序中的 timeit 函数就可当做是 decorator, 它take(接收)的f是一个函数, 返回的wrapper也是一个函数.
返回的wrapper是干什么的呢?
1> 首先, wrapper与被装饰的my_func、other_func take(接收) 的参数应该一样.
2> 此处的wrapper函数的逻辑是: 为了方便阐述,我们将 decorator take的函数叫做A.
记录一个时间点, 再运行A并记录其返回值, 接着打印A的运行时间, 最后将A的返回值返回.
来看看运行结果
1.005234956741333
None
9.059906005859375e-06
6
2
3
4
刚才那个示例中的decorator只能装饰只有一个参数的函数. 为了提高通用性, 我们应该这么做:
用允许变长的函数参数, *args
、**kwargs
, 这样就可以将装饰器应用到有若干个参数的函数上. (*≧ω≦)
import time
def timeit(f):
def wrapper(*args, **kwargs):
start = time.time()
ret = f(*args, **kwargs)
print(time.time() - start)
return ret
return wrapper
@timeit
def my_func(x):
time.sleep(x)
@timeit
def other_func(x, y):
return x + y
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
我们再进阶一下, 带参数的 decorator , 那是怎么回事呢?
其实带参数的decorator, 也没有任何神奇的地方, 只不过 "相当于 在等价的过程中 多了一次函数调用"!
import time
def timeit(iteration): # iteration:运行多少次.
def inner(f):
def wrapper(*args, **kwargs):
start = time.time()
for _ in range(iteration):
f(*args, **kwargs)
print(time.time() - start)
return
return wrapper
return inner
@timeit(100000)
def other_func(x, y):
return x + y
if __name__ == '__main__':
print(other_func(1, 2))
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
原来是不带参数的装饰器, @timeit
--> other_func = timeit(other_func)
现在是带参数的装饰器, @timeit(100000)
--> other_func = timeit(100000)(other_func)
不难看出, 无参装饰器, 参数是函数, 返回值也是函数;
有参装饰器, 参数是你可以任意指定的东西, 然后它要返回一个函数a, 这个函数a的参数是函数, 返回值也是函数.