文章目录
一、什么是装饰器
-
这个名词定义是这样的:
-
装饰器是可调用的对象,其参数是另一个函数(被装饰的函数)。装饰器可能会执行被装饰的函数并返回,或者将其替换成另一个函数。
二、装饰器返回的两种情况示例
2.1 f中有调用g(x)
def decorate(func):
def f(*args, **kwargs):
res = func(*args, **kwargs) * 2
print('Execuet f: g(x)*2')
return res
return f
@decorate
def g(x):
print('Execuet g(x)')
return x**2
"""
>>> g(5)
Execuet g(x)
Execuet f: g(x)*2
50
"""
很显然,我们看到g(x)是被嵌入在f中执行的。
2.2 f中没有调用g(x)
def decorate(func):
def f(*args, **kwargs):
print('Execuet f')
return f
@decorate
def g(x):
print('Execuet g(x)')
return x**2
"""
>>> g(5)
Execuet f
"""
三、装饰器为什么叫装饰器
显然装饰器的装饰两个字,应该会是有些文章的。不然就这样吧g(x)嵌入到f中,好像看着也没啥花头。所以,我们借用他这框架,来实现一下对g(x)的装饰。
- 增加一些文字装饰
def decorate(func):
def f(*args, **kwargs):
if len(args) == 1:
args_str = str(args)
else:
args_str = ','.join(args)
print(f'好了,接下来我们要执行g({args_str})啦')
res = func(*args, **kwargs)
print(f'好了,g({args_str})执行好了,结果是: {res}。')
return res
return f
@decorate
def g(x):
print('Execuet g(x)')
return x**2
"""
>>> g(5)
好了,接下来我们要执行g((5,))啦
Execuet g(x)
好了,g((5,))执行好了,结果是: 25。
25
"""
很显然,上面的装饰只是个小小示例,没啥应用价值。在我们进行特征工程,或者一些自己写的封装任务中,我们都比较关注该任务的执行时间。
所以,其实我们是可以对一些必要的函数做装饰,计算该函数的运行时间,而不是在每个函数里都重复的写计算该函数运行时间的小脚本。
3.1 实用的clock装饰器
from functools import wraps
import time
def clock(func):
# 把func的相关属性复制到clocked
@wraps(func)
def clocked(*args, **kwargs):
start = time.perf_counter()
res = func(*args, **kwargs)
cost_seconds = time.perf_counter() - start
msg = f'func: {func.__name__}, cost: {cost_seconds:.5f}'
print(msg)
return res
return clocked
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import load_iris
iris = load_iris()
@clock
def scale_data(df):
scale = StandardScaler()
return scale.fit_transform(df)
iris_dt_scale = scale_data(iris.data)
"""
>>> iris_dt_scale = scale_data(iris.data)
func: scale_data, cost: 0.00044
"""
四、标准库中的装饰器
4.1 使用functools.lru_cache做备忘
lru_cache (Least Recently Used cached)实现了备忘的功能。是一个优化技术,它把耗时的函数的结果保存起来,避免闯入相同的参数时重复计算。递归优化lru_cache(maxsize=128, typed=False)
-
注:
-
为了得到最佳性能应该用2的幂次 做 maxsize
typed =True 不同类型不同管理 如(1, 1.0)
lru_cache 斐波那契数优化
- 未加lru_cache,fib(6) 递归
from functools import wraps
import time
def clock(func):
@wraps(func)
def clocked(*args, **kwargs):
args_str = ','.join([repr(arg) for arg in args])
start = time.perf_counter()
res = func(*args, **kwargs)
cost_seconds = time.perf_counter() - start
msg = f'func: {func.__name__}({args_str}), cost: {cost_seconds:.5f}'
print(msg)
return res
return clocked
@clock
def fib(n):
if n < 2:
return n
return fib(n-2) + fib(n - 1)
fib(6)
"""
>>> fib(6)
func: fib(0), cost: 0.00000
func: fib(1), cost: 0.00000
func: fib(2), cost: 0.00055
func: fib(1), cost: 0.00000
func: fib(0), cost: 0.00000
func: fib(1), cost: 0.00000
func: fib(2), cost: 0.00031
func: fib(3), cost: 0.00525
func: fib(4), cost: 0.00626
func: fib(1), cost: 0.00000
func: fib(0), cost: 0.00000
func: fib(1), cost: 0.00000
func: fib(2), cost: 0.00174
func: fib(3), cost: 0.00224
func: fib(1), cost: 0.00000
func: fib(2), cost: 0.00040
func: fib(1), cost: 0.00000
func: fib(0), cost: 0.00000
func: fib(1), cost: 0.00000
func: fib(2), cost: 0.00031
func: fib(3), cost: 0.00064
func: fib(4), cost: 0.00137
func: fib(5), cost: 0.00414
func: fib(6), cost: 0.01092
8
"""
可以看到,递归的时候很多都进行了重复运算
- 加入lru_cache,fib(6) 递归
from functools import wraps, lru_cache
@clock
@lru_cache(maxsize=128, typed=False)
def fib(n):
if n < 2:
return n
return fib(n-2) + fib(n - 1)
"""
>>> fib(6)
func: fib(0), cost: 0.00000
func: fib(1), cost: 0.00000
func: fib(2), cost: 0.00232
func: fib(1), cost: 0.00000
func: fib(2), cost: 0.00000
func: fib(3), cost: 0.00051
func: fib(4), cost: 0.00340
func: fib(3), cost: 0.00000
func: fib(4), cost: 0.00000
func: fib(5), cost: 0.00036
func: fib(6), cost: 0.00418
8
"""
4.2 使用functools.singledispatch做泛函数
泛函数,顾名思义,就是泛化能力很强,很广泛的意思。但是,我们都知道,一些函数只能进入特定的参数,进入其他类型的参数会报错。
比如 g(x): x**2
这个函数,显然str类型的参数是无法传入的。但是,我们有想要实现这个参数的传入,或者说是不同参数传入,我对这个参数的处理会有略微的不同,这个时候如果用try-except
写起来会比较繁琐。
所以,我们可以引入singledispatch
,对我们要泛化的函数进行修饰。然后用 函数.register(str)
去写str参数进入函数的时候的处理。
- 我们来看下一个简单的实例。当
g(x): x**2
这个函数进入参数是str
时,返回x*len(x)**2
from functools import singledispatch
@singledispatch
def g(x):
return x**2
@g.register(str)
def _(x):
return x*len(x)**2
"""
>>> g('aa'), g(2)
('aaaaaaaa', 4)
"""
参考
《Fluent Python》