查看: 198|回复: 0

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

[复制链接]
发表于 1 小时前 | 显示全部楼层 |阅读模式
functools 是 Python 标准库中处理函数和可调用对象的高阶工具集合。原文列出的核心导入包括 reduce、partial、lru_cache、wraps、total_ordering、singledispatch、cmp_to_key、cached_property。它常用于缓存优化、偏函数、装饰器元信息保留、函数重载和比较方法补全。

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

partial(func, *args, **kwargs) 可以固定部分参数,创建一个新的可调用对象,相当于 Python 中函数柯里化的一种实现。基础示例如下:
  1. from functools import partial
  2. def power(base, exponent):
  3.     return base ** exponent
  4. square = partial(power, exponent=2)
  5. cube = partial(power, exponent=3)
  6. print(square(10))  # 100
  7. print(cube(10))    # 1000
复制代码

在实际回调场景中,可以用 partial 固定 logger 名称,再继续固定 event_type 和时间戳,减少重复传参:
  1. from functools import partial
  2. import time
  3. def log_event(logger, event_type, message, timestamp):
  4.     print(f'[{timestamp}] {event_type}: {message} (logger={logger})')
  5. app_log = partial(log_event, 'app')
  6. app_log_error = partial(app_log, 'ERROR', timestamp=time.time())
  7. app_log_error('数据库连接失败')
  8. # [1718000000.0] ERROR: 数据库连接失败 (logger=app)
复制代码

数据处理管道也适合用 partial 生成专用转换函数:
  1. from functools import partial
  2. def transform(data, multiplier, offset, round_digits=2):
  3.     return round(data * multiplier + offset, round_digits)
  4. c_to_f = partial(transform, multiplier=9/5, offset=32, round_digits=1)
  5. f_to_c = partial(transform, multiplier=5/9, offset=-32*5/9, round_digits=1)
  6. print(c_to_f(0))    # 32.0
  7. print(c_to_f(100))  # 212.0
  8. print(f_to_c(32))   # 0.0
复制代码

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

lru_cache 用于最近最少使用缓存;cache 是 Python 3.9+ 提供的无限制缓存,等价于 lru_cache(maxsize=None)。用 lru_cache 改写递归函数,可以避免重复子问题计算:
  1. from functools import lru_cache
  2. import time
  3. @lru_cache(maxsize=128)
  4. def fibonacci(n):
  5.     if n < 2:
  6.         return n
  7.     return fibonacci(n - 1) + fibonacci(n - 2)
  8. start = time.perf_counter()
  9. print(fibonacci(35))
  10. print(f'首次: {time.perf_counter() - start:.4f}秒')
  11. start = time.perf_counter()
  12. print(fibonacci(35))
  13. print(f'缓存: {time.perf_counter() - start:.6f}秒')
  14. print(fibonacci.cache_info())
  15. # CacheInfo(hits=34, misses=36, maxsize=128, currsize=36)
  16. fibonacci.cache_clear()
复制代码

使用 lru_cache 时要注意:被缓存函数的参数必须可哈希;lru_cache 装饰器不能直接用于方法,否则可能导致内存泄漏。数据库查询缓存是常见场景:
  1. from functools import lru_cache
  2. @lru_cache(maxsize=256)
  3. def get_user_by_id(user_id):
  4.     print(f'查询数据库: user_id={user_id}')
  5.     return {'id': user_id, 'name': f'用户{user_id}'}
  6. print(get_user_by_id(1))  # 查询数据库
  7. print(get_user_by_id(1))  # 缓存命中,不查数据库
复制代码

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

写装饰器时,如果不使用 wraps,原函数的 __name__、__doc__ 等元信息会被 wrapper 覆盖:
  1. from functools import wraps
  2. def bad_decorator(func):
  3.     def wrapper(*args, **kwargs):
  4.         '''这是wrapper的文档'''
  5.         return func(*args, **kwargs)
  6.     return wrapper
  7. @bad_decorator
  8. def greet(name):
  9.     '''向用户打招呼'''
  10.     return f'Hello, {name}!'
  11. print(greet.__name__)  # wrapper
  12. print(greet.__doc__)   # 这是wrapper的文档
复制代码

加上 @wraps(func) 后,元信息能够正确保留:
  1. from functools import wraps
  2. def good_decorator(func):
  3.     @wraps(func)
  4.     def wrapper(*args, **kwargs):
  5.         '''这是wrapper的文档'''
  6.         return func(*args, **kwargs)
  7.     return wrapper
  8. @good_decorator
  9. def greet_v2(name):
  10.     '''向用户打招呼'''
  11.     return f'Hello, {name}!'
  12. print(greet_v2.__name__)  # greet_v2
  13. print(greet_v2.__doc__)   # 向用户打招呼
复制代码

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

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

singledispatch 可以根据第一个参数类型选择不同实现,类似单分派函数重载:
  1. from functools import singledispatch
  2. @singledispatch
  3. def format_output(data):
  4.     return str(data)
  5. @format_output.register(list)
  6. def _(data):
  7.     return '[' + ', '.join(format_output(item) for item in data) + ']'
  8. @format_output.register(dict)
  9. def _(data):
  10.     items = [f'{k}: {format_output(v)}' for k, v in data.items()]
  11.     return '{' + ', '.join(items) + '}'
  12. @format_output.register(int)
  13. def _(data):
  14.     return f'{data:,}'
  15. print(format_output(1234567))                  # 1,234,567
  16. print(format_output([1, 'hello', [2, 3]]))      # [1, hello, [2, 3]]
  17. print(format_output({'name': '张三', 'age': 25}))  # {name: 张三, age: 25}
复制代码

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

cached_property 把耗时计算变成实例属性风格访问,第一次访问时计算并缓存,后续访问直接取缓存:
  1. from functools import cached_property
  2. import time
  3. class DataAnalyzer:
  4.     def __init__(self, data):
  5.         self.data = data
  6.     @cached_property
  7.     def statistics(self):
  8.         print('正在计算统计数据...')
  9.         time.sleep(0.5)
  10.         return {
  11.             'sum': sum(self.data),
  12.             'avg': sum(self.data) / len(self.data),
  13.             'min': min(self.data),
  14.             'max': max(self.data),
  15.         }
  16. analyzer = DataAnalyzer([1, 2, 3, 4, 5])
  17. print(analyzer.statistics)  # 计算,并打印“正在计算...”
  18. print(analyzer.statistics)  # 缓存命中,不再打印
复制代码

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

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

total_ordering 适合实现版本号等可比较对象。只要定义 __eq__ 和 __lt__,其余比较方法会自动生成:
  1. from functools import total_ordering
  2. @total_ordering
  3. class Version:
  4.     def __init__(self, major, minor, patch):
  5.         self.major = major
  6.         self.minor = minor
  7.         self.patch = patch
  8.     def __eq__(self, other):
  9.         return (self.major, self.minor, self.patch) == (other.major, other.minor, other.patch)
  10.     def __lt__(self, other):
  11.         return (self.major, self.minor, self.patch) < (other.major, other.minor, other.patch)
  12.     def __repr__(self):
  13.         return f'v{self.major}.{self.minor}.{self.patch}'
  14. v1 = Version(1, 2, 3)
  15. v2 = Version(2, 0, 0)
  16. print(v1 < v2)   # True
  17. print(v1 <= v2)  # True,自动生成
  18. print(v1 >= v2)  # False,自动生成
  19. print(v1 != v2)  # True,自动生成
复制代码

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

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

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-9-25 13:54 , Processed in 0.021751 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部