Effective Python 装饰器
在Python,函数属于First Class Object,而First Class Object可以:
- 作为参数进行传递
- 作为返回值
- 将其赋值给其它变量
- 存放在某种数据结构中,比如list、tuple等
1 Decorator
下面介绍三种类型的装饰器
1.1 装饰器没有参数,被装饰函数没有返回值
import time
import math
def calculate_time(func):
# args is short for arguments, kwargs is short for keywords arguments
def inner1(*args, **kwargs):
begin = time.time()
func(*args, **kwargs)
end = time.time()
print("Total time taken in {}: {}".format(func.__name__, end - begin))
return inner1
@calculate_time
def factorial(num):
time.sleep(3)
print(math.factoria(num))
# calling function factorial
factorial(10)
# 等价形式
(calculate_time)(factorial)(10)
= inner1(10)
1.2 装饰器没有参数,被装饰函数有返回值
# 其实和上面的情况没有啥差别, 这个的inner1函数有返回值罢了
def hello_decorator(func):
def inner1(*args, **kwargs):
print("Before Execution")
res = func(*args, **kwargs)
print("After Execution")
return res
return inner1
@hello_decorator
def sum_two_numbers(a, b):
print("Inside the function")
return a + b
res = sum_two_numbers(1, 2)
= (hello_decorator)(sum_two_numbers)(1, 2)
= inner1(1, 2)
1.3 装饰器带有参数
参考资料:
《Decorators with parameters in Python: Example 3》
# Python code to illustrate
# Decorators with parameters in Python (Multi-level Decorators)
def decodecorator(dataType, message1, message2):
def decorator(fun):
print(message1)
def wrapper(*args, **kwargs):
print(message2)
if all([type(arg) == dataType for arg in args]):
return fun(*args, **kwargs)
return "Invalid Input"
return wrapper
return decorator
@decodecorator(str, "Decorator for 'stringJoin'", "stringJoin started ...")
def stringJoin(*args):
st = ''
for i in args:
st += i
return st
@decodecorator(int, "Decorator for 'summation'\n", "summation started ...")
def summation(*args):
summ = 0
for arg in args:
summ += arg
return summ
print(stringJoin("I ", 'like ', "Geeks", 'for', "geeks"))
print()
print(summation(19, 2, 8, 533, 67, 981, 119))
理解带有参数的装饰器,需要明白以下两点
@decorator
def func_name():
...
# 和
decorator(func_name)是等价的
@decorator(param)
def func_name():
...
# 和
[decorator(param)](func_name)是等价的
记录个常用的计时装饰器
import time
from functools import wraps
def cal_func_time(show_time=True):
"""
Calculate running time of functions, and print the time
"""
def my_decorator(func):
@wraps(func)
def measure_time(*args, **kwargs):
t1 = time.time()
res = func(*args, **kwargs)
t2 = time.time()
if show_time:
print("Running time of Function {}: {}".format(func.__name__, t2 - t1))
return res
return measure_time
return my_decorator
对于functools.wraps 的作用,《What does functools.wraps do?》解释得非常清楚。
1.4 总结
- 装饰器的典型行为是:把被装饰的函数替换成新的函数,二者接受相同的参数,而且(通常)返回被装饰的函数本该返回的值,同时还会做些额外的操作。所以上面第三部分的decorator(func)才是真正的装饰器,装饰器的参数是一个函数。
- 由于装饰器是函数,所以可以组合起来使用。
@d1
@d2
def f():
print('f')
# 等价于
def f():
print('f')
f = d1(d2(f))
- 当我们讨论decorator时,我们已经接触到一点函数式编程了。因为函数式编程的特点之一是使用高阶函数。高阶函数是接受函数作为参数,或者把函数作为结果返回的函数。
2 functools模块
functools模块作用于高阶函数,在介绍decorator时,我们使用了functools.wraps()
装饰器来勾结行为良好的装饰器,这里再介绍《流畅的python》一书中重点介绍的functools.lru_cache()
和functools.singledispatch()
两个装饰器。
2.1 functools.lru_cache
from functools import lru_cache, singledispatch
import time
# lru_cache()可以用来优化递归算法
@lru_cache(maxsize=30)
def fibonacci_with_lru_cache(num):
return 1 if num < 2 else fibonacci_with_lru_cache(num - 2) + fibonacci_with_lru_cache(num - 1)
def fibonacci(num):
return 1 if num < 2 else fibonacci(num - 2) + fibonacci(num - 1)
if __name__ == "__main__":
num = 30
st = time.time()
fibonacci(num)
ed = time.time()
print("Time: {}".format(ed - st))
st = time.time()
fibonacci_with_lru_cache(num)
ed = time.time()
print("Time: {}".format(ed - st))