持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情
介绍
functools是Python 内置的模块,通常情况下functools模块中的功能适用于所有可调用的对象。该模块定义了很多方法,这些方法通常都是被当做装饰器来使用的。本文主要介绍以下三种方法的使用:
wrapscachecached_property
使用
wraps
该装饰器通常用在我们定义装饰器时使用,装饰器的目的就是为了给被装饰对象添加新的功能,但是不能修改被装饰对象的源代码和调用方式,装饰器的原理其实就是偷梁换柱,但是单纯的使用装饰器存在一点点的小瑕疵,比如下述代码示例:
当打印被装饰过的函数的函数名或者被装饰函数有说明文档的时候,如果打印出来就会暴露调用的函数并不是原函数了。
import time
def timer(func):
def wrapper(*args,**kwargs):
start_time = time.time()
print('hello')
time.sleep(2)
end = time.time()
print(end-start_time)
res = func(*args,**kwargs)
return res
return wrapper
@timer
def index(x,y):
'''
doc:我是index
:param x:
:param y:
:return:
'''
print(x,y)
index(1,2)
print(index.__name__) # wrapper
print(index.__doc__) # None
'''
原函数有说明文档,被装饰后没有了
原函数的函数名是index,只要已打印函数的名字属性,就会暴露不是原函数的事实
'''
解决上述问题有两种方案:
- 方式一 - 手动将原函数的属性赋值给wrapper函数
使用这种方式有一个非常明显的缺点,就是每个函数的属性有很多,难道真的要一条一条的进行修改吗?
# 1、函数wrapper.__name__ = 原函数.__name__
# 2、函数wrapper.__doc__ = 原函数.__doc__
wrapper.__name__ = func.__name__
wrapper.__doc__ = func.__doc__
- 方式二 - 使用
wraps装饰器
wraps本质也是一个装饰器,会将原函数的属性全部赋值给wrapper函数,实现完美的偷梁换柱。
from functools import wraps
def outer(func):
# 将被装饰函数当做参数传给wraps
@wrapper(func)
def wrapper(*args,**kwargs)
res = func(*args,**kwargs)
return res
return wrapper
cache
缓存功能,当函数被cache装饰之后,调用函数后得到的结果会被存放在缓存中,下次调用该函数并且返回值相等时就不会重复执行该函数,而是在缓存中获取该值。
from functools import cache
@cache
def factorial(n):
return n * factorial(n-1) if n else 1
>>> factorial(10) # 没有缓存结果,递归调用11次
3628800
>>> factorial(5) # 结果已经存在缓存中,只需要在缓存中获取结果,无需在执行函数获取结果
120
>>> factorial(12) # 只需要递归调用两次,前10次结果已经存放在缓存中
479001600
cached_property
cached_property 是一个装饰器,作用是将方法转换为特征属性,一次性的计算该方法的值之后,将其存入缓存中,调用方法是会被当做一个属性进行调用,在该值不可变的高计算资源消耗的情况下该装饰器非常有用,因为只要计算过一次之后,就会将结果直接保存,再次使用时直接从缓存中取出即可。
如果不需要该缓存值,可以通过delattr进行属性删除,就可以再次调用装饰器装饰的方法了。
from functools import cached_property
class DataSet:
def __init__(self, sequence_of_numbers):
self._data = tuple(sequence_of_numbers)
@cached_property
def stdev(self):
return statistics.stdev(self._data)
同样的我们可以通过cache和property这两个装饰器实现和cached_property装饰器类似的效果:
from functools import cached
class DataSet:
def __init__(self, sequence_of_numbers):
self._data = sequence_of_numbers
@property
@cache
def stdev(self):
return statistics.stdev(self._data)
总结
本文主要介绍了functools模块的三个方法,结合我们实际代码需求进行灵活的选择即可,functools模块下的方法非常多,基本上都是为可调用对象服务的,有兴趣的小伙伴可以在官方网站上了解更多该模块的方法。