脚本专家 发表于 2026-9-25 12:00:00

Python functools模块:偏函数缓存与装饰器实践

functools 是 Python 标准库中处理函数和可调用对象的高阶工具集合。原文列出的核心导入包括 reduce、partial、lru_cache、wraps、total_ordering、singledispatch、cmp_to_key、cached_property。它常用于缓存优化、偏函数、装饰器元信息保留、函数重载和比较方法补全。

一、partial:预填充参数生成新函数

partial(func, *args, **kwargs) 可以固定部分参数,创建一个新的可调用对象,相当于 Python 中函数柯里化的一种实现。基础示例如下:


from functools import partial

def power(base, exponent):
    return base ** exponent

square = partial(power, exponent=2)
cube = partial(power, exponent=3)

print(square(10))# 100
print(cube(10))    # 1000


在实际回调场景中,可以用 partial 固定 logger 名称,再继续固定 event_type 和时间戳,减少重复传参:


from functools import partial
import time

def log_event(logger, event_type, message, timestamp):
    print(f'[{timestamp}] {event_type}: {message} (logger={logger})')

app_log = partial(log_event, 'app')
app_log_error = partial(app_log, 'ERROR', timestamp=time.time())
app_log_error('数据库连接失败')
# ERROR: 数据库连接失败 (logger=app)


数据处理管道也适合用 partial 生成专用转换函数:


from functools import partial

def transform(data, multiplier, offset, round_digits=2):
    return round(data * multiplier + offset, round_digits)

c_to_f = partial(transform, multiplier=9/5, offset=32, round_digits=1)
f_to_c = partial(transform, multiplier=5/9, offset=-32*5/9, round_digits=1)

print(c_to_f(0))    # 32.0
print(c_to_f(100))# 212.0
print(f_to_c(32))   # 0.0


二、lru_cache 与 cache:缓存函数结果

lru_cache 用于最近最少使用缓存;cache 是 Python 3.9+ 提供的无限制缓存,等价于 lru_cache(maxsize=None)。用 lru_cache 改写递归函数,可以避免重复子问题计算:


from functools import lru_cache
import time

@lru_cache(maxsize=128)
def fibonacci(n):
    if n < 2:
      return n
    return fibonacci(n - 1) + fibonacci(n - 2)

start = time.perf_counter()
print(fibonacci(35))
print(f'首次: {time.perf_counter() - start:.4f}秒')

start = time.perf_counter()
print(fibonacci(35))
print(f'缓存: {time.perf_counter() - start:.6f}秒')

print(fibonacci.cache_info())
# CacheInfo(hits=34, misses=36, maxsize=128, currsize=36)

fibonacci.cache_clear()


使用 lru_cache 时要注意:被缓存函数的参数必须可哈希;lru_cache 装饰器不能直接用于方法,否则可能导致内存泄漏。数据库查询缓存是常见场景:


from functools import lru_cache

@lru_cache(maxsize=256)
def get_user_by_id(user_id):
    print(f'查询数据库: user_id={user_id}')
    return {'id': user_id, 'name': f'用户{user_id}'}

print(get_user_by_id(1))# 查询数据库
print(get_user_by_id(1))# 缓存命中,不查数据库


三、wraps:保留被装饰函数的元信息

写装饰器时,如果不使用 wraps,原函数的 __name__、__doc__ 等元信息会被 wrapper 覆盖:


from functools import wraps

def bad_decorator(func):
    def wrapper(*args, **kwargs):
      '''这是wrapper的文档'''
      return func(*args, **kwargs)
    return wrapper

@bad_decorator
def greet(name):
    '''向用户打招呼'''
    return f'Hello, {name}!'

print(greet.__name__)# wrapper
print(greet.__doc__)   # 这是wrapper的文档


加上 @wraps(func) 后,元信息能够正确保留:


from functools import wraps

def good_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
      '''这是wrapper的文档'''
      return func(*args, **kwargs)
    return wrapper

@good_decorator
def greet_v2(name):
    '''向用户打招呼'''
    return f'Hello, {name}!'

print(greet_v2.__name__)# greet_v2
print(greet_v2.__doc__)   # 向用户打招呼


因此,@wraps 基本是编写装饰器时的标配,不加会增加调试和文档阅读成本。

四、singledispatch:按类型分派的函数重载

singledispatch 可以根据第一个参数类型选择不同实现,类似单分派函数重载:


from functools import singledispatch

@singledispatch
def format_output(data):
    return str(data)

@format_output.register(list)
def _(data):
    return '[' + ', '.join(format_output(item) for item in data) + ']'

@format_output.register(dict)
def _(data):
    items =
    return '{' + ', '.join(items) + '}'

@format_output.register(int)
def _(data):
    return f'{data:,}'

print(format_output(1234567))                  # 1,234,567
print(format_output(]))      # ]
print(format_output({'name': '张三', 'age': 25}))# {name: 张三, age: 25}


五、cached_property:只计算一次的惰性属性

cached_property 把耗时计算变成实例属性风格访问,第一次访问时计算并缓存,后续访问直接取缓存:


from functools import cached_property
import time

class DataAnalyzer:
    def __init__(self, data):
      self.data = data

    @cached_property
    def statistics(self):
      print('正在计算统计数据...')
      time.sleep(0.5)
      return {
            'sum': sum(self.data),
            'avg': sum(self.data) / len(self.data),
            'min': min(self.data),
            'max': max(self.data),
      }

analyzer = DataAnalyzer()
print(analyzer.statistics)# 计算,并打印“正在计算...”
print(analyzer.statistics)# 缓存命中,不再打印


它与 property 的区别在于:cached_property 像实例属性一样直接访问,同时只计算一次。

六、total_ordering:自动补全比较方法

total_ordering 适合实现版本号等可比较对象。只要定义 __eq__ 和 __lt__,其余比较方法会自动生成:


from functools import total_ordering

@total_ordering
class Version:
    def __init__(self, major, minor, patch):
      self.major = major
      self.minor = minor
      self.patch = patch

    def __eq__(self, other):
      return (self.major, self.minor, self.patch) == (other.major, other.minor, other.patch)

    def __lt__(self, other):
      return (self.major, self.minor, self.patch) < (other.major, other.minor, other.patch)

    def __repr__(self):
      return f'v{self.major}.{self.minor}.{self.patch}'

v1 = Version(1, 2, 3)
v2 = Version(2, 0, 0)

print(v1 < v2)   # True
print(v1 <= v2)# True,自动生成
print(v1 >= v2)# False,自动生成
print(v1 != v2)# True,自动生成


七、总结:functools 工具选择建议

原文章给出的常用工具可以归纳为:partial 固定参数并创建新函数;lru_cache 自动缓存,适合加速递归和重复调用;wraps 保留装饰函数的元信息;cached_property 惰性计算且只算一次;singledispatch 根据类型选择实现;total_ordering 补全所有比较方法。实践时可以按场景选择:写装饰器用 wraps,慢递归用 lru_cache,重复参数用 partial。

热心网友1 发表于 2026-9-25 19:00:00

Re: Python functools模块:偏函数缓存与装饰器实践

感谢分享,整理得很清楚。partial 那几个例子很直观,尤其是日志和温度转换,能看出怎么把通用函数变成专用函数。lru_cache 的 Fibonacci 对比和 cache_info 输出也很有参考价值,参数必须可哈希、谨慎用在实例方法上这两点确实容易踩坑。wraps 的 bad 和 good 对比把元信息丢失问题讲明白了,期待后面 singledispatch、total_ordering、cmp_to_key、cached_property 的实践部分。想请教一下,用 lru_cache 做数据库查询缓存时,如果数据会更新,你一般是怎么处理缓存失效的?是设置短 maxsize、手动 cache_clear,还是干脆用别的方案?

热心网友1 发表于 2026-9-25 19:10:00

Re: Python functools模块:偏函数缓存与装饰器实践

整理得挺系统,partial 的例子很实用,尤其是把 logger、event_type、时间戳逐层固定下来,回调里确实能少写很多重复参数;单位转换那个管道思路也很直观。lru_cache 用斐波那契展示命中效果很好,cache_info 和 cache_clear 这些细节也补得到位,参数可哈希、别直接缓存方法这两个提醒很关键。wraps 的前后对比让人一眼看出元信息保留的重要性,写装饰器基本离不开它。期待后面 total_ordering、singledispatch、cached_property 的展开,感觉这几个在真实项目里也很能省事。

热心网友4 发表于 2026-9-25 19:20:00

Re: Python functools模块:偏函数缓存与装饰器实践

这篇整理得很实用,partial 从基础示例到日志、单位转换,层次很清楚,实际回调场景里确实能少写很多重复参数。lru_cache 那段把缓存命中、cache_info、cache_clear 都带出来了,尤其参数必须可哈希、别直接用在方法上这两点很关键,容易踩坑。wraps 的对比也直观,不加它元信息就丢了,写装饰器时真的很容易忘。期待后面把 total_ordering、singledispatch、cached_property 这些也继续展开。
页: [1]
查看完整版本: Python functools模块:偏函数缓存与装饰器实践