Python函数式编程10 functools模块:高阶函数工具

functools 库包含许多实用的函数操作工具,并且它们都是高阶函数。

常用函数

lru_cache:缓存结果

@lru_cache(maxsize) 装饰器能保存已有计算结果,从而加快函数运行,其中 LRU 指最近使用的(least recent used) 。该装饰器会将最近得到的计算结果保留在缓存中,并在必要时清除不常用的计算结果。

例如,以下是使用缓存改进后的递归形式的斐波那契数列函数:

@lru_cache(maxsize=1024)
def fibonacci_cached(n):
    if n < 2:
        return n
    else:
        return fibonacci_cached(n - 1) + fibonacci_cached(n - 2)

接下来在 IPython 环境中对有无缓存的情况测试执行效率,结果为:

In [1]: def fibonacci_without_cache(): ...: return fibonacci(34) ...: def fibonacci_with_cache(): ...: fibonacci_cached.cache_clear() ...: return fibonacci_cached(34) In [2]: %timeit fibonacci_without_cache 2.12 s ± 7.21 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) In [3]: %timeit fibonacci_with_cache 12.6 µs ± 67.8 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

注意,不能直接使用 %timeit 命令测试函数,因为它会多次执行取平均时间,而第一次执行后由于缓存的存在使后续结果直接从缓存中获取,干扰测试效果。因此,每次测试后都通过装饰器的 .cache_clear() 方法清空缓存。

从测试结果中可以看出,缓存的存在极大提升了递归函数的执行效率。

partial:冻结参数

partial(func, /, *args, **kwargs) 可以将函数的部分参数固定,得到一个新的函数。

例如,如果想要设置打开一个文件时,永远是以 UTF-8 编码的形式打开,那么可以使用 partialopen() 函数的 encoding 参数固定,形成一个新的函数:

uopen = partial(open, encoding='utf8')

这样每次确保都是以 UTF-8 编码对文件操作的:

with uopen('demo.txt', 'w') as f:
    f.write('🙁✯⛈⚝☂☁︎😶')

partial() 非常适用于冻结高阶函数的函数参数,这种情况下,可以提前写好一个函数,从而让一个比较泛化的高阶函数具有更具体的功能,例如:

from operator import add, mul

sum = partial(reduce, add)
prod = partial(reduce, mul)

该装饰器的本质就是给一部分参数提前赋值,也可以使用匿名函数实现类似的效果。

singledispatch:单分派

@singledispatch 装饰器可以根据参数的类型指定不同的函数,可以在一定程度上使用泛型。

例如,假设要实现一个函数 scale(data, scalar) ,将数据缩放。但是传入的数据可能是一个数值,也可能是一个数组。那么就需要在函数内判断传入数据的类型。

@singledispatch 装饰器则可以简化判断的流程,它可以根据参数类型的不同,指定调用不同的函数。为了实现这一点,需要先将目标函数装饰起来:

@singledispatch
def scale(data, scalar):
    ...

然后调用目标函数的 @.register(type) 方法作为装饰器,将不同类型的参数导向不同的函数:

@scale.register(float)
def _scale(data: float, scalar):
    return data * scalar

@scale.register(list)
def _scale(data: list, scalar):
    return list(map(lambda x: x * scalar, data))

此时,如果传入的参数类型不同,那么就会被不同的函数调用了:

>>> scale(0.7, 2) 1.4 >>> scale(list(range(10)), 0.3) [0.0, 0.3, 0.6, 0.8999999999999999, 1.2, 1.5, 1.7999999999999998, 2.1, 2.4, 2.6999999999999997]

如果对一个分派函数同时使用多个装饰器,那么这些类型的参数都会汇合到这个函数中:

@scale.register(tuple)
@scale.register(list)
def _scale(data, scalar):

Python3.7 对该函数做了一些改进,如果已经提供了类型注解,那么可以直接使用方法对象作为装饰器,即 @scale.register 这种不调用的形式。

这种单分派函数可以在一定程度上实现函数签名效果,但可惜的是它只支持一个参数。

除此之外,functools 还包含一系列其它的高阶函数,例如第 4 节中介绍到的 reduce() 函数、在介绍面向对象编程时装饰类的 @total_ordering 装饰器、Python3.9 新增的 @cache 装饰器,以及以上提到的装饰器都有用于方法的改版。更多标准库的介绍可以参阅 Python 标准库官方文档。

参考资料

https://docs.python.org/3/library/functools.html

Python3 标准库 functools 官方文档

京ICP备2021034974号
contact me by hello@frozencandles.fun