functools
库包含许多实用的函数操作工具,并且它们都是高阶函数。
常用函数
lru_cache:缓存结果
@lru_cache(maxsize)
装饰器能保存已有计算结果,从而加快函数运行,其中 LRU 指最近使用的(least recent used) 。该装饰器会将最近得到的计算结果保留在缓存中,并在必要时清除不常用的计算结果。
例如,以下是使用缓存改进后的递归形式的斐波那契数列函数:
接下来在 IPython 环境中对有无缓存的情况测试执行效率,结果为:
注意,不能直接使用 %timeit
命令测试函数,因为它会多次执行取平均时间,而第一次执行后由于缓存的存在使后续结果直接从缓存中获取,干扰测试效果。因此,每次测试后都通过装饰器的 .cache_clear()
方法清空缓存。
从测试结果中可以看出,缓存的存在极大提升了递归函数的执行效率。
partial:冻结参数
partial(func, /, *args, **kwargs)
可以将函数的部分参数固定,得到一个新的函数。
例如,如果想要设置打开一个文件时,永远是以 UTF-8 编码的形式打开,那么可以使用 partial
将 open()
函数的 encoding
参数固定,形成一个新的函数:
这样每次确保都是以 UTF-8 编码对文件操作的:
partial()
非常适用于冻结高阶函数的函数参数,这种情况下,可以提前写好一个函数,从而让一个比较泛化的高阶函数具有更具体的功能,例如:
该装饰器的本质就是给一部分参数提前赋值,也可以使用匿名函数实现类似的效果。
singledispatch:单分派
@singledispatch
装饰器可以根据参数的类型指定不同的函数,可以在一定程度上使用泛型。
例如,假设要实现一个函数 scale(data, scalar)
,将数据缩放。但是传入的数据可能是一个数值,也可能是一个数组。那么就需要在函数内判断传入数据的类型。
@singledispatch
装饰器则可以简化判断的流程,它可以根据参数类型的不同,指定调用不同的函数。为了实现这一点,需要先将目标函数装饰起来:
然后调用目标函数的 @.register(type) 方法作为装饰器,将不同类型的参数导向不同的函数:
此时,如果传入的参数类型不同,那么就会被不同的函数调用了:
如果对一个分派函数同时使用多个装饰器,那么这些类型的参数都会汇合到这个函数中:
Python3.7 对该函数做了一些改进,如果已经提供了类型注解,那么可以直接使用方法对象作为装饰器,即 @scale.register
这种不调用的形式。
这种单分派函数可以在一定程度上实现函数签名效果,但可惜的是它只支持一个参数。
除此之外,functools 还包含一系列其它的高阶函数,例如第 4 节中介绍到的 reduce()
函数、在介绍面向对象编程时装饰类的 @total_ordering
装饰器、Python3.9 新增的 @cache
装饰器,以及以上提到的装饰器都有用于方法的改版。更多标准库的介绍可以参阅 Python 标准库官方文档。
参考资料
https://docs.python.org/3/library/functools.html
Python3 标准库 functools
官方文档