functools 包详解

📅 2026/7/7 19:49:35
functools 包详解
定位functools是 Python 函数式编程与装饰器体系的基石。它解决了“如何安全、高效地操作函数”这一核心问题。核心价值减少重复代码、提升运行效率、规范装饰器写法。第一部分核心 API 全景图谱类别工具一句话解释使用频率缓存lru_cache记住函数结果避免重复计算⭐⭐⭐⭐⭐cached_property把昂贵的计算变成“只读属性”⭐⭐⭐⭐变换partial冻结参数制造新函数⭐⭐⭐⭐partialmethod类中的partial⭐⭐⭐装饰器wraps保护原函数的“身份证”⭐⭐⭐⭐⭐update_wrapperwraps的底层实现⭐⭐分发singledispatch根据类型重载函数⭐⭐⭐singledispatchmethod类方法中的类型重载⭐⭐⭐辅助reduce累积计算左折叠⭐⭐cmp_to_key兼容旧式比较函数⭐⭐total_ordering简化类的比较运算符⭐⭐第二部分核心工具深度解析1. 缓存机制性能利器lru_cache(Least Recently Used)原理哈希表存储结果链表维护访问顺序。当缓存满时淘汰最久未使用的项。fromfunctoolsimportlru_cachelru_cache(maxsize128,typedFalse)deffibonacci(n):ifn2:returnnreturnfibonacci(n-1)fibonacci(n-2)# 缓存管理fibonacci.cache_clear()# 清空infofibonacci.cache_info()# 命中率监控maxsize: 缓存数量。None为无限慎用内存泄漏风险。typed:True时1和1.0视为不同参数。前提参数必须可哈希Hashable。cached_property原理首次访问时计算结果存入实例字典后续访问直接返回实例字典中的值。fromfunctoolsimportcached_propertyclassDataPipeline:def__init__(self,raw_data):self.raw_dataraw_datacached_propertydefcleaned_data(self):print(Expensive cleaning...)return[x.strip()forxinself.raw_data]objDataPipeline([ a , b ])obj.cleaned_data# 计算一次obj.cleaned_data# 直接取值区别不同于property每次访问都计算也不同于手动_cache None代码臃肿。2. 函数变换逻辑复用partial(偏函数)原理冻结函数的部分位置参数或关键字参数返回一个新的可调用对象。fromfunctoolsimportpartialdefsend_request(method,url,headersNone,timeout10):...# 创建一个新的“专用”函数post_jsonpartial(send_request,POST,headers{Content-Type:application/json})# 调用时只需传入剩余参数post_json(/api/users,timeout30)优势比lambda可读性更强且保留了函数的__name__属性。partialmethod专门用于类的方法定义避免子类重写时的样板代码。fromfunctoolsimportpartialmethodclassConnection:defconnect(self,proto,host,port):...httppartialmethod(connect,http,port80)httpspartialmethod(connect,https,port443)3. 装饰器安全wraps痛点装饰器会覆盖原函数的元信息__name__,__doc__导致日志、调试和反射出错。fromfunctoolsimportwrapsdefmy_decorator(func):wraps(func)# 关键defwrapper(*args,**kwargs):Wrapper docreturnfunc(*args,**kwargs)returnwrappermy_decoratordefimportant_func():Important docpassprint(important_func.__name__)# important_func (而不是 wrapper)print(important_func.__doc__)# Important doc黄金法则写装饰器必加wraps。4. 函数分发替代 If/Elifsingledispatch原理根据第一个参数的类型分发到不同的实现函数。fromfunctoolsimportsingledispatchsingledispatchdefserialize(obj):raiseTypeError(fUnknown type:{type(obj)})serialize.registerdef_(obj:dict):returnfDict:{obj}serialize.registerdef_(obj:list):returnfList:{len(obj)}items好处符合开闭原则。新增类型支持时无需修改原主函数。singledispatchmethod用于类中的方法分发常见于 Form 验证或 API 序列化。第三部分Web 开发实战FastAPI Django1. FastAPI 场景场景 A全局配置官方推荐痛点每次请求都读取磁盘或环境变量非常低效。# config.pyfromfunctoolsimportlru_cachelru_cache(maxsize1)defget_settings():# 假设这是一个昂贵的 I/O 操作return{db_url:sqlite:///test.db}# main.pyapp.get(/)defread_root(settingsDepends(get_settings)):returnsettings场景 B依赖注入参数化痛点同一个认证逻辑Admin 和普通用户需要的权限不同。fromfastapiimportDependsfromfunctoolsimportpartialdefget_user(token:str,role:str):...get_adminpartial(get_user,roleadmin)get_user_deppartial(get_user,roleuser)app.get(/admin,dependencies[Depends(get_admin)])defadmin_dashboard():...场景 C响应序列化痛点避免 View 层充斥大量的isinstance判断。# 使用 singledispatch 统一处理不同类型的返回值singledispatchdefto_json_response(data):returnJSONResponse(contentstr(data))to_json_response.register(User)def_(user:User):returnJSONResponse(content{id:user.id,name:user.name})2. Django 场景场景 A模板 Tags防 N1痛点模板循环中重复查询数据库。# templatetags/my_tags.pyfromdjangoimporttemplatefromfunctoolsimportlru_cache registertemplate.Library()register.simple_taglru_cache(maxsize256)defget_category_name(category_id):# 这个查询在单次渲染中被缓存returnCategory.objects.get(idcategory_id).name场景 BSignals 参数固化痛点Django Signal 只能接收(sender, instance, **kwargs)无法直接传参。fromfunctoolsimportpartialfromdjango.db.models.signalsimportpost_savedefnotify_channel(instance,channel):send_notification(instance,channel)# 固化参数notify_emailpartial(notify_channel,channelemail)notify_smspartial(notify_channel,channelsms)post_save.connect(notify_email,senderOrder)场景 CMiddleware 装饰器痛点自定义中间件导致 View 元数据丢失。defrole_required(view_func):wraps(view_func)# 必须否则 DRF/Admin 报错def_wrapped(request,*args,**kwargs):ifnotrequest.user.is_staff:returnHttpResponseForbidden()returnview_func(request,*args,**kwargs)return_wrapped第四部分避坑指南非常重要❌ 坑 1lru_cache缓存了“请求级”数据现象用户 A 看到用户 B 的数据。原因Web 服务器是常驻进程lru_cache默认是全局的。对策只缓存全局不变数据如配置、常量、静态映射。用户相关数据使用cached_property或 Session。❌ 坑 2cached_property遇到可变对象现象对象内容变了但缓存值没变。代码classCart:cached_propertydeftotal(self):returnsum(item.priceforiteminself.items)defadd_item(self,item):self.items.append(item)# total 依然是旧值对策明确该属性是否真的适合缓存。如果必须缓存在修改数据时手动删除缓存del self.total。❌ 坑 3装饰器顺序错误规则越靠近函数定义的装饰器越先执行包装。正确顺序# 逻辑上是用 lru_cache 缓存 被 wraps 保护的 wrapper 的结果lru_cachewraps(func)deffunc():...错误顺序wraps(func)lru_cache# 这里 wraps 缓存了一个可能被包装过的函数逻辑混乱deffunc():...第五部分决策速查表需求首选工具理由递归、DB 查询、API 调用lru_cache空间换时间指数级提速昂贵计算的属性cached_property延迟加载请求内复用依赖注入/回调函数参数固定partial代码整洁意图明确写装饰器wraps防止元数据丢失必选替代if/elif类型判断singledispatch符合开闭原则易维护类中实现比较大小 ( )total_ordering少写 4 个魔法方法总结functools不仅仅是一个工具箱它代表了一种声明式、组合式的编程思维。在 Web 开发中性能善用lru_cache和cached_property。架构用singledispatch解耦业务逻辑。规范永远使用wraps保护你的装饰器。