杰瑞科技汇

Python的functools模块是做什么用的?

functools 是 Python 标准库中的一个核心模块,它提供了用于操作函数和可调用对象的高阶函数,它包含了一些非常实用且强大的工具,可以帮助你以更函数式、更优雅、更高效的方式编写代码。

Python的functools模块是做什么用的?-图1
(图片来源网络,侵删)

这个模块中的函数主要分为几大类:装饰器、高阶函数和比较工具,下面我们逐一进行详细说明。


核心装饰器

functools 中的装饰器是其最常用和最强大的功能之一,它们可以动态地修改或增强函数的行为。

functools.wraps

这是一个“装饰器的装饰器”,用于保留原始函数的元信息(如函数名、文档字符串、参数列表等)。

问题场景: 当你使用一个简单的装饰器时,原始函数的元信息会被替换为装饰器内部函数的信息。

Python的functools模块是做什么用的?-图2
(图片来源网络,侵删)
def my_decorator(func):
    def wrapper(*args, **kwargs):
        """Wrapper function's docstring."""
        print("Something is happening before the function is called.")
        result = func(*args, **kwargs)
        print("Something is happening after the function is called.")
        return result
    return wrapper
@my_decorator
def example():
    """This is a docstring for the example function."""
    print("This is the example function.")
print(example.__name__)  # 输出: wrapper
print(example.__doc__)   # 输出: Wrapper function's docstring.

可以看到,example 函数的 __name____doc__ 都丢失了。

解决方案:使用 @functools.wraps

import functools
def my_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        """Wrapper function's docstring."""
        print("Something is happening before the function is called.")
        result = func(*args, **kwargs)
        print("Something is happening after the function is called.")
        return result
    return wrapper
@my_decorator
def example():
    """This is a docstring for the example function."""
    print("This is the example function.")
print(example.__name__)  # 输出: example
print(example.__doc__)   # 输出: This is a docstring for the example function.

example 函数的元信息被正确地保留了。这是一个好习惯,几乎在所有自定义装饰器中都应使用 @functools.wraps


functools.lru_cache

这是一个非常有用的装饰器,用于实现“最近最少使用”(Least Recently Used)缓存,它可以缓存函数的调用结果,当再次使用相同的参数调用该函数时,它会直接从缓存中返回结果,而无需重新计算,从而极大地提高性能。

Python的functools模块是做什么用的?-图3
(图片来源网络,侵删)

适用场景:

  • 纯函数(对于相同的输入,总是产生相同的输出)。
  • 计算成本高昂的函数。
  • 函数会被频繁地使用重复的参数调用。

示例:计算斐波那契数列

import functools
import time
@functools.lru_cache(maxsize=128) # maxsize 是缓存的最大条目数,None 表示无限制
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)
# 第一次调用,会进行计算并缓存
start_time = time.time()
print(f"fibonacci(35) = {fibonacci(35)}")
end_time = time.time()
print(f"第一次耗时: {end_time - start_time:.4f} 秒")
# 第二次调用,使用相同的参数,直接从缓存中获取,速度极快
start_time = time.time()
print(f"fibonacci(35) = {fibonacci(35)}")
end_time = time.time()
print(f"第二次耗时: {end_time - start_time:.4f} 秒")
# 查看缓存信息
print(fibonacci.cache_info()) # 输出: CacheInfo(hits=34, misses=36, maxsize=128, currsize=36)
# hits: 缓存命中次数
# misses: 缓存未命中次数

可以看到,第二次调用几乎瞬间完成。lru_cache 在递归计算中效果尤其显著。


functools.singledispatch

这是一个强大的工具,可以实现“单分派泛函数”(Single-Dispatch Generic Functions),它允许你根据函数第一个参数的类型来执行不同的逻辑,而无需使用一堆 if-elif-else 语句。

问题场景: 假设你想编写一个函数,可以处理不同类型的输入,并执行不同的操作。

# 传统方式,笨拙且难以扩展
def process(data):
    if isinstance(data, int):
        print(f"Processing an integer: {data * 2}")
    elif isinstance(data, str):
        print(f"Processing a string: {data.upper()}")
    elif isinstance(data, list):
        print(f"Processing a list: {len(data)} items")
    else:
        print(f"Processing an unknown type: {type(data)}")
process(10)
process("hello")
process([1, 2, 3])

解决方案:使用 @functools.singledispatch

from functools import singledispatch
import numbers
@singledispatch
def process(data):
    """Base function for processing data."""
    print(f"Processing an unknown type: {type(data)}")
@process.register # 注册针对 int 的具体实现
def _(data: int):
    print(f"Processing an integer: {data * 2}")
@process.register # 注册针对 str 的具体实现
def _(data: str):
    print(f"Processing a string: {data.upper()}")
@process.register # 注册针对 list 的具体实现
def _(data: list):
    print(f"Processing a list: {len(data)} items")
# 也可以注册给多个类型
@process.register(numbers.Real)
def _(data: float):
    print(f"Processing a float: {data ** 2}")
# 调用
process(10)
process("hello")
process([1, 2, 3])
process(3.14)

这种方式更加清晰、模块化和易于扩展,每个类型的处理逻辑都可以独立定义和维护。


functools.total_ordering

如果你正在创建一个自定义类,并想让它支持比较操作(如 <, >, <=, >=, , ),你通常需要实现所有这些方法,这个装饰器可以让你只需实现 __eq__ 和其他任意一个(__lt__, __le__, __gt__, __ge__),它会自动为你推导出其余的比较方法。

示例:

from functools import total_ordering
@total_orderding
class Student:
    def __init__(self, name, grade):
        self.name = name
        self.grade = grade
    def __eq__(self, other):
        if not isinstance(other, Student):
            return NotImplemented
        return self.grade == other.grade
    def __lt__(self, other):
        if not isinstance(other, Student):
            return NotImplemented
        return self.grade < other.grade
# Student 类自动拥有了 >, <=, >= 的功能
s1 = Student("Alice", 85)
s2 = Student("Bob", 92)
s3 = Student("Charlie", 85)
print(s1 < s2)  # True
print(s1 > s2)  # True (自动推导)
print(s1 == s3) # True
print(s1 <= s3) # True (自动推导)
print(s1 >= s3) # True (自动推导)

高阶函数

functools.partial

partial 用于“冻结”一个函数的部分参数,创建一个新的、可调用的 partial 对象,这可以看作是创建一个简化版函数的快捷方式。

语法: functools.partial(func, *args, **keywords)

示例:

import functools
import math
# 创建一个只需要一个参数的 new_func,它内部已经固定了 base=2
new_func = functools.partial(math.pow, 2)
# 调用 new_func 只需提供一个指数参数
print(new_func(8))   # 输出: 256.0 (即 2^8)
print(new_func(10))  # 输出: 1024.0 (即 2^10)
# 也可以冻结关键字参数
greet = functools.partial(print, "Hello, ", sep="---")
greet("World") # 输出: Hello, ---World

实际应用场景: 当需要将一个多参数函数作为回调函数传递给另一个只接受单参数函数时,partial 非常有用,在 sorted()map() 中。

# 按单词长度排序
words = ['apple', 'banana', 'cherry', 'date']
sorted_words = sorted(words, key=functools.partial(len))
print(sorted_words) # 输出: ['date', 'apple', 'banana', 'cherry']

比较工具

functools.cmp_to_key

在 Python 3 中,sorted()list.sort() 函数的 key 参数取代了 Python 2 中的 cmp 参数。key 函数接受一个元素并返回一个用于比较的值,而 cmp 函数接受两个元素并返回比较结果(-1, 0, 1)。

cmp_to_key 是一个桥梁,它可以将一个旧的 cmp 风格的比较函数转换成一个 key 函数。

示例:

假设我们有一个旧的比较函数,想按字符串的“元音字母数量”排序。

def compare_vowel_count(a, b):
    # 计算字符串中元音的数量
    def count_vowels(s):
        return sum(1 for c in s if c in "aeiou")
    count_a = count_vowels(a)
    count_b = count_vowels(b)
    if count_a < count_b:
        return -1
    elif count_a > count_b:
        return 1
    else:
        return 0
# 使用 cmp_to_key 将 cmp 函数转换为 key 函数
from functools import cmp_to_key
words = ['hello', 'world', 'apple', 'why', 'python']
sorted_words = sorted(words, key=cmp_to_key(compare_vowel_count))
print(sorted_words)
# 输出: ['why', 'world', 'python', 'hello', 'apple']
# 解释: 'why' (1), 'world' (1), 'python' (1), 'hello' (2), 'apple' (2)

函数/装饰器 主要用途 典型场景
@wraps 保留被装饰函数的元信息 所有自定义装饰器的“标配”。
@lru_cache 缓存函数结果,加速重复调用 递归函数、纯函数、计算密集型任务。
@singledispatch 根据第一个参数的类型分发逻辑 编写能处理多种数据类型的通用函数。
@total_ordering 简化自定义类的比较方法实现 只需实现 __eq__ 和一个比较运算符即可获得全套比较功能。
partial 固定函数的部分参数,创建新函数 简化函数调用,适配需要特定格式的回调函数。
cmp_to_key 将旧的 cmp 比较函数转为 key 函数 在 Python 3 中使用遗留的比较逻辑。

functools 是 Python 工具箱中的一个瑞士军刀,掌握它的核心功能,尤其是几个关键的装饰器,能让你的代码更 Pythonic、更高效、更具可读性和可维护性。

分享:
扫描分享到社交APP
上一篇
下一篇