知识点简介
functools 是 Python 标准库中专门为高阶函数服务的模块,内置了一系列用于函数式编程和装饰器开发的实用工具。掌握 functools 能让你写出更简洁、更 Pythonic、性能更好的代码。本文覆盖最常用的 4 个工具:@wraps、@lru_cache、partial 和 @singledispatch。
核心工具详解
1. @functools.wraps --- 保留元信息的装饰器
写装饰器时,被装饰的函数会丢失原名、文档字符串、注解等信息。@wraps 负责把这些元信息拷贝回来。
python
import functools
def log_call(func):
@functools.wraps(func) # 保留 func 的 __name__, __doc__ 等
def wrapper(*args, **kwargs):
print(f"调用 {func.__name__}")
return func(*args, **kwargs)
return wrapper
@log_call
def greet(name: str) -> str:
"""向某人打招呼"""
return f"你好, {name}"
print(greet.__name__) # greet(没有 @wraps 会返回 wrapper)
print(greet.__doc__) # 向某人打招呼
print(greet.__annotations__) # {'name': <class 'str'>, 'return': <class 'str'>}
2. @functools.lru_cache --- 记忆化缓存
自动缓存函数的返回值,相同参数再次调用直接返回缓存结果。适合纯函数和无副作用的递归或计算密集型操作。
python
import functools
import time
@functools.lru_cache(maxsize=128)
def fib(n: int) -> int:
"""带缓存的斐波那契数列"""
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
start = time.time()
print(fib(100)) # 354224848179261915075
print(f"耗时: {time.time() - start:.6f}s") # 瞬间完成
# 查看缓存统计
print(fib.cache_info()) # CacheInfo(hits=98, misses=101, maxsize=128, currsize=101)
注意 :
@lru_cache的参数必须是可哈希 的。如果参数是 list、dict 等可变类型,需要先转成 tuple 或使用@cache(Python 3.9+)。
3. functools.partial --- 偏函数冻结
固定一个函数的某些参数,生成一个新函数。适用于回调、配置化场景。
python
import functools
def power(base: float, exp: float) -> float:
return base ** exp
# 创建平方和立方偏函数
square = functools.partial(power, exp=2)
cube = functools.partial(power, exp=3)
print(square(5)) # 25
print(cube(5)) # 125
# 在排序中固定 key 函数
data = [(1, "z"), (3, "a"), (2, "m")]
sorted(data, key=functools.partial(lambda k, i: k[i], i=1))
# 按第二个元素排序: [(3, 'a'), (2, 'm'), (1, 'z')]
4. @functools.singledispatch --- 单分派泛函数
根据第一个参数的类型执行不同的函数体。替代大量 if/elif isinstance(...) 的链式判断。
python
import functools
@functools.singledispatch
def serialize(obj):
"""将对象序列化为字符串"""
raise TypeError(f"不支持的类型: {type(obj)}")
@serialize.register(int)
def _(obj: int) -> str:
return f"数字: {obj}"
@serialize.register(str)
def _(obj: str) -> str:
return f"字符串: '{obj}'"
@serialize.register(list)
def _(obj: list) -> str:
return f"列表: {[serialize(x) for x in obj]}"
@serialize.register(dict)
def _(obj: dict) -> str:
return f"字典: {{{', '.join(f'{k!r}: {v}' for k, v in obj.items())}}}"
# 测试
print(serialize(42)) # 数字: 42
print(serialize("hello")) # 字符串: 'hello'
print(serialize([1, "a"])) # 列表: ['数字: 1', "字符串: 'a'"]
综合使用示例
python
import functools
import time
from typing import Any
def timer_and_cache(func=None, *, maxsize: int = 128):
"""组合了计时和缓存的装饰器工厂"""
def decorator(f):
@functools.wraps(f)
@functools.lru_cache(maxsize=maxsize)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = f(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{f.__name__} 耗时 {elapsed:.6f}s")
return result
wrapper.original = f # 保留原始函数引用
return wrapper
return decorator(func) if func else decorator
@timer_and_cache
def compute(n: int) -> int:
"""模拟复杂计算"""
time.sleep(0.5)
return n * n
print(compute(10)) # 首次 500ms+
print(compute(10)) # 缓存命中,瞬间返回
print(compute.cache_info()) # CacheInfo(hits=1, misses=1, ...)
注意事项 / 避坑指南
-
@lru_cache不要装饰有副作用的方法:如果函数依赖外部状态(文件、数据库、全局变量),缓存会导致返回过期结果。 -
@wraps不是万能的 :它仅拷贝__module__、__name__、__qualname__、__annotations__、__doc__和__dict__。签名信息(inspect.signature)不会自动恢复,如需保留完整签名配合@decorator库或inspect手动处理。 -
partial对象不是普通函数 :functools.partial返回的对象没有__name__属性(除非手动设置),也不支持注解。检查类型时用callable()而非isfunction()。 -
singledispatch只对第一个参数生效 :如果需要根据多个参数分派,考虑functools.singledispatchmethod(Python 3.8+)或使用类型模式匹配(Python 3.10+)。 -
缓存泄漏内存 :
@lru_cache(maxsize=None)会无限增长,慎用。对于预期大量不同参数调用的场景,设置合理的maxsize或考虑@cache+ 手动清除策略。