目录:
缓存函数结果 lru_cache
创建部分应用函数 partial
对序列进行累积操作 reduce
将比较函数转换为键函数 cmp_to_key
保留被装饰函数的元数据 wraps
在 Python 编程中,functools
模块提供了一系列高阶函数和工具类,用于操作或组合函数。它可以帮助我们编写更简洁、更具可读性的代码,并且能够提升程序的性能。functools
中的功能通常都是围绕着函数式编程的概念设计的,如装饰器(decorators)、偏函数(partial functions)等。
缓存函数结果 lru_cache
functools.lru_cache
是一个非常实用的装饰器,它可以为函数提供缓存功能,避免重复计算,提高函数的执行效率。LRU(Least Recently Used)表示最近最少使用,它会自动管理缓存,当缓存满了时,会删除最近最少使用的缓存项。
下面是一个简单的例子,计算斐波那契数列:
import functools
# 使用 lru_cache 装饰器
@functools.lru_cache(maxsize=None)
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
# 计算第 30 个斐波那契数
result = fibonacci(30)
print(result) # 输出:832040
在这个例子中,我们定义了一个 fibonacci
函数来计算斐波那契数列。由于斐波那契数列的计算有很多重复的子问题,使用 functools.lru_cache
装饰器后,函数会自动缓存已经计算过的结果,下次再计算相同的参数时,直接从缓存中获取结果,大大提高了计算效率。
在一些计算量较大且输入参数相同的情况下,functools.lru_cache
可以显著提高程序的性能。比如,在数据分析中,我们可能需要频繁计算一些复杂的统计指标,这些指标的计算依赖于相同的数据集,使用 lru_cache
可以避免重复计算,加快数据分析的速度。
创建部分应用函数 partial
functools.partial
函数的作用是固定函数的某些参数,返回一个新的函数,这个新函数的调用会更加简单。
from functools import partial
def greet(greeting, name):
return f"{greeting}, {name}!"
say_hello = partial(greet, greeting="你好") # 固定greeting参数为"你好"
print(say_hello(name="山海摸鱼人")) # 输出:你好, 山海摸鱼人!
这里我们创建了一个名为say_hello
的部分应用函数,它总是以“你好”作为问候语。
在一些需要频繁调用某个函数,但部分参数固定不变的情况下,functools.partial
就非常有用。比如,我们在处理文件操作时,经常需要打开文件,并且指定文件的编码格式。如果我们总是使用相同的编码格式,就可以使用 partial
固定这个参数:
import functools
# 假设我们总是使用 'utf-8' 编码打开文件
open_utf8 = functools.partial(open, encoding='utf-8')
# 现在使用 open_utf8 打开文件就不需要再指定编码格式
with open_utf8('test.txt', 'r') as f:
content = f.read()
print(content)
对序列进行累积操作 reduce
functools.reduce
函数会对一个可迭代对象进行累积计算。它接受一个函数和一个可迭代对象作为参数,函数需要接受两个参数,reduce
会将这个函数作用于可迭代对象的元素上,从左到右依次计算,最终返回一个累积的结果。
下面是一个计算列表元素乘积的例子:
import functools
# 定义一个计算乘积的函数
def multiply(x, y):
return x * y
# 定义一个列表
numbers = [1, 2, 3, 4]
# 使用 reduce 计算列表元素的乘积
product = functools.reduce(multiply, numbers)
print(product) # 输出:24
在这个例子中,multiply
函数接受两个参数并返回它们的乘积。functools.reduce
会将 multiply
函数依次作用于 numbers
列表的元素上,即先计算 1 * 2
,得到 2,再将 2 与 3 相乘,得到 6,最后将 6 与 4 相乘,得到最终结果 24。
functools.reduce
在很多需要对数据进行累积计算的场景中都非常有用。比如,计算一个列表中所有字符串的总长度:
import functools
# 定义一个列表
strings = ['apple', 'banana', 'cherry']
# 使用 reduce 计算所有字符串的总长度
total_length = functools.reduce(lambda x, y: x + len(y), strings, 0)
print(total_length) # 输出:17
在这个例子中,我们使用了一个匿名函数 lambda x, y: x + len(y)
作为 reduce
的第一个参数,这个函数将前一个累积结果 x
加上当前字符串 y
的长度。初始值设为 0。
将比较函数转换为键函数 cmp_to_key
functools.cmp_to_key
是一个能巧妙解决复杂排序问题的工具。
在Python 3
中,内置的排序方法要求的是一个键函数,而不是比较函数。functools.cmp_to_key
帮助我们解决这个问题,它可以把传统的比较函数转换成适合现代Python排序的键函数。
对整数列表排序
我们先来看一个简单的示例,对一个整数列表进行排序:
import functools
def custom_compare(a, b):
if a < b:
return-1
elif a == b:
return0
else:
return1
numbers = [5, 3, 8, 1, 9]
sorted_numbers = sorted(numbers, key=functools.cmp_to_key(custom_compare))
print(sorted_numbers) # [1, 3, 5, 8, 9]
在这个例子中,我们定义了一个 custom_compare
比较函数,然后使用 functools.cmp_to_key
将其转换为键函数,最后将这个键函数传递给 sorted
函数进行排序。
对自定义对象排序
接下来,我们看一个更复杂的示例,对自定义对象进行排序。假设“山海摸鱼人”团队的成员摸鱼时,每个人有不同的摸鱼方式和摸鱼时长,我们可以创建一个表示摸鱼人的类,根据摸鱼时长和摸鱼方式的优先级来排序。
import functools
class FishMonger:
def __init__(self, name, way, duration):
self.name = name
self.way = way
self.duration = duration
def compare_fish_mongers(monger1, monger2):
# 先按摸鱼时长比较,时长越长越靠前
if monger1.duration > monger2.duration:
return-1
elif monger1.duration == monger2.duration:
# 如果时长相同,按摸鱼方式的某种优先级比较
# 这里简单假设方式的首字母顺序靠前的优先
if monger1.way < monger2.way:
return-1
elif monger1.way == monger2.way:
return0
else:
return1
else:
return1
fish_mongers = [
FishMonger("山海摸鱼人A", "刷剧", 3),
FishMonger("山海摸鱼人B", "看小说", 2),
FishMonger("山海摸鱼人C", "刷剧", 3)
]
sorted_fish_mongers = sorted(fish_mongers, key=functools.cmp_to_key(compare_fish_mongers))
for monger in sorted_fish_mongers:
print(f"Name: {monger.name}, Way: {monger.way}, Duration: {monger.duration}")
在这个例子中,我们定义了一个 FishMonger
类,然后定义了一个 compare_fish_mongers
比较函数,用于比较两个摸鱼人的摸鱼情况。接着,我们使用 functools.cmp_to_key
将这个比较函数转换为键函数,最后对摸鱼人列表进行排序并打印结果。
保留被装饰函数的元数据 wraps
functools.wraps
是一个装饰器,它的主要作用是在装饰器函数中保留被装饰函数的元数据,比如函数名、文档字符串等。
下面是一个简单的例子:
import functools
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("Before function call")
result = func(*args, **kwargs)
print("After function call")
return result
return wrapper
@my_decorator
def greet(name):
"""This function greets a person."""
returnf"Hello, {name}!"
print(greet.__name__) # 输出:greet
print(greet.__doc__) # 输出:This function greets a person.
在这个例子中,定义了一个装饰器 my_decorator
,它在被装饰函数调用前后打印一些信息。如果不使用 functools.wraps
,greet
函数的元数据会被 wrapper
函数的元数据覆盖,导致 greet.__name__
输出 wrapper
,greet.__doc__
输出 None
。使用 functools.wraps
后,greet
函数的元数据得以保留。
在开发项目时,会使用装饰器来为函数添加一些通用的功能,如日志记录、性能监测等。使用 functools.wraps
可以确保被装饰函数的元数据不被破坏,方便代码的调试和维护。