目录
8. functools.cached_property (Python 3.8+)
functools.cached_property的实现原理
Python 是一门多功能语言,其众多优点之一就是它有很多功能强大的内置模块,这些模块使我们程序员在很多时候不用重复造轮子。functools
模块就是这样一个典型的例子。充分利用它可以让我们的 Python 代码更整洁、清晰和专业。
本文将介绍 Python 这个杰出模块的 8 个必知用途。阅读后,相信你能够在“Python 大师”的路上更进一步。
1. functools.partial
创建一个函数的新版本,并固定部分参数,减少重复传递参数的次数。类似于方法重写。
Python 有很多易用的内置函数,但是有时候对于特殊的案例我们必须向它们添加特定的参数。比如,int()
函数可以用于将字符串转换为整数。如果待转换的字符串是一个二进制整数的话,我们必须添加第二个参数:
print(f"The binary integer 101101 is equal: {int('101101', base=2)}")
# The binary integer 101101 is equal: 45
如果我们需要频繁地使用这个二进制转换功能,却总是重复写入相同的第二个参数,那将是很烦人的(同时看起来也是蠢蠢的)。
那么有没有一种方法可以让我们一处实现多处使用呢?functools
中的 partial
方法刚好可以帮助我们达到这个目的,让我们的代码变得更简洁:
from functools import partial
binary_trans = partial(int, base=2)
print(f"101101 (base 2) is equal to: {binary_trans('101101')} (base 10)")
print(f"1111110 (base 2) is equal to: {binary_trans('1111110')} (base 10)")
print(f"11111111 (base 2) is equal to: {binary_trans('11111111')} (base 10)")
输出结果如下:
101101 (base 2) is equal to: 45 (base 10)
1111110 (base 2) is equal to: 126 (base 10)
11111111 (base 2) is equal to: 255 (base 10)
这样一来,不管后续调用多少次,我们都不必给出第二个参数,实现定制化转换功能。当然,对于自定义函数,也同样可以使用 partial
方法。
from functools import partial
def multiply(x, y):
return x * y
double = partial(multiply, 2)
print(double(5)) # 输出 10
这里的 partial
实现了某个数值 2 倍的定制化计算功能。
2. functools.lru_cache
使用最近最少使用(Least Recently Used, LRU)缓存装饰器,提高函数的性能,尤其适用于计算密集型或 I/O 密集型函数。
下面看一个常见的例子,即斐波那契数列数字的计算,我们分别用常规方法和缓存方法实现,看看它们之间的性能差距:
from functools import lru_cache
import timeit
def fibonacci_norm(n):
if n < 2:
return n
return fibonacci_norm(n - 1) + fibonacci_norm(n - 2)
@lru_cache(maxsize=32)
def fibonacci_lru_cached(n):
if n < 2:
return n
return fibonacci_lru_cached(n - 1) + fibonacci_lru_cached(n - 2)
def timed_fibonacci_norm():
resutl = fibonacci_norm(30)
return resutl
def timed_fibonacci_cached():
resutl = fibonacci_lru_cached(30)
# 清除缓存结果
fibonacci_lru_cached.cache_clear()
return resutl
if __name__ == '__main__':
time_norm = timeit.timeit(timed_fibonacci_norm, number=50)
time_cached = timeit.timeit(timed_fibonacci_cached, number=50)
print(f'Average time for fibonacci (norm): {time_norm / 50} s')
print(f'Average time for fibonacci (cached): {time_cached / 50} s')
print(f'Cached version is faster than norm: {time_norm / time_cached / 50}')
执行结果如下:
Average time for fibonacci (norm): 0.1954923020000024 s
Average time for fibonacci (cached): 1.077199999599543e-05 s
Cached version is faster than norm: 362.9637988724064
从上面的结果可以看出,相较于常规方法,使用 lru_cache
方法速度快了大约 363 倍,对性能的提升非常显著。而且随着计算数字的增大,这个差距还会继续扩大。
此外,缓存方法还有另一种实现方式,即 cache
装饰器。其实现方式与 lru_cache
类似。我们可以看看它与常规方法的性能差距:
from functools import cache
import timeit
@cache
def fibonacci_cached(n):
if n < 2:
return n
return fibonacci_cached(n - 1) + fibonacci_cached(n - 2)
def timed_fibonacci_cached():
resutl = fibonacci_lru_cached(30)
# 重新装饰函数以清楚缓存
fibonacci_cached = cache(fibonacci_lru_cached)
return resutl
if __name__ == '__main__':
time_norm = timeit.timeit(timed_fibonacci_norm, number=50)
time_cached = timeit.timeit(timed_fibonacci_cached, number=50)
print(f'Average time for fibonacci (norm): {time_norm / 50} s')
print(f'Average time for fibonacci (cached): {time_cached / 50} s')
print(f'Cached version is faster than norm: {time_norm / time_cached / 50}')
可以看出,cache
的性能更好,比常规方法快了约 1213 倍,主要是因为 lru_cache
存在缓存策略,所以在缓存管理上会有一定开销。
lru_cache
和 cache
各有其优点和适用场景,性能上的差异主要取决于具体的使用场景和缓存策略的需求。下面是一些对比和选择建议:
lru_cache
的特点
-
LRU 缓存策略:
lru_cache
使用最近最少使用(Least Recently Used, LRU)策略,当缓存达到最大容量时,会自动清除最久未使用的条目。这对于需要限制缓存大小并且期望自动管理缓存淘汰的场景非常有用。 -
缓存统计信息:
lru_cache
提供缓存命中率等统计信息(通过cache_info
方法),有助于监控和优化缓存使用。 -
性能:在缓存大小有限且需要频繁访问缓存条目的情况下,
lru_cache
能够显著提升性能,但在缓存管理上会有一定的开销。
cache
的特点
-
无缓存策略:
cache
是一个简单的无策略缓存,它没有缓存淘汰机制,即缓存条目会一直保留,直到程序终止或显式清除。这适合于需要缓存所有结果且不需要考虑内存限制的场景。 -
性能:
cache
的性能开销较小,因为它没有管理缓存条目的开销,在没有内存限制和缓存淘汰需求的情况下,cache
可以提供更好的性能。
性能比较与选择
-
内存限制:如果你的应用需要限制内存使用,并且缓存数据量可能很大,选择
lru_cache
更为合适,因为它能够自动管理缓存大小并清除旧条目。 -
缓存条目数量少:如果缓存的数据量较少,且不会超出内存限制,使用
cache
会更加简单高效,因为它没有缓存管理的开销。 -
缓存管理需求:如果你需要了解缓存的使用情况和命中率,
lru_cache
提供的统计信息会很有帮助。 -
性能测试:对于特定应用场景,可以通过实际测试来比较两者的性能。可以使用
timeit
模块进行多次调用的时间测量,评估两者在实际应用中的表现。
3. functools.reduce
functools.reduce
是 Python 很重要的高阶函数之一,它用于对可迭代对象中的元素进行累计操作,最终将其简化为单一的值。reduce
可以说是一个“归约”函数,通过对序列中的元素依次应用指定的二元操作,将序列归约为一个值。
functools.reduce
的作用
reduce
的作用是对序列进行二元操作,并将序列简化为一个单一的值。它的使用格式如下:
from functools import reduce
result = reduce(function, iterable[, initializer])
-
function:一个接受两个参数的函数,
reduce
会将其应用于iterable
的元素。 -
iterable:一个可迭代对象(如列表、元组等)。
-
initializer(可选):初始值,如果提供,则首先将其与序列的第一个元素一起传递给