functools 全面教程:常用 API 串联与实战指南
functools 是 Python 标准库中专注于高阶函数(操作/封装函数/可调用对象的函数)的核心工具库,它弥补了基础语法在函数封装、参数绑定、缓存、归约、比较逻辑定义等场景的不足。本文将系统拆解 functools 中最常用的 API,从原理、语法到实战示例逐一讲解,并通过综合案例串联所有核心用法,帮助你彻底掌握这个高频库。
环境准备
Python 3.5+(singledispatch需 3.4+,lru_cache的typed参数需 3.3+),无需额外安装,直接通过import functools导入即可使用。
核心 API 详解
1. functools.wraps:保留装饰器的函数元信息
作用
装饰器在封装原函数时,会覆盖原函数的__name__、__doc__、__module__等元信息,导致调试、文档查看时丢失关键信息。wraps是装饰器工具,用于保留原函数的元信息。
语法
@functools.wraps(wrapped,assigned=WRAPPER_ASSIGNMENTS,updated=WRAPPER_UPDATES)wrapped:被装饰的原函数;assigned:要复制的元信息列表(默认包含__module__、__name__、__doc__等);updated:要更新的属性列表(默认是__dict__)。
示例:对比有无 wraps 的差异
importfunctools# 无 wraps 的装饰器deflog_no_wraps(func):defwrapper(*args,**kwargs):print(f"调用函数:{func.__name__}")returnfunc(*args,**kwargs)returnwrapper# 有 wraps 的装饰器deflog_with_wraps(func):@functools.wraps(func)# 关键:保留原函数元信息defwrapper(*args,**kwargs):print(f"调用函数:{func.__name__}")returnfunc(*args,**kwargs)returnwrapper@log_no_wrapsdefadd(a,b):"""计算两数之和"""returna+b@log_with_wrapsdefmul(a,b):"""计算两数之积"""returna*b# 测试元信息print("无 wraps:",add.__name__,add.__doc__)# 输出:无 wraps: wrapper Noneprint("有 wraps:",mul.__name__,mul.__doc__)# 输出:有 wraps: mul 计算两数之积2. functools.lru_cache:函数结果缓存
作用
缓存函数的调用结果(适用于纯函数:输入相同则输出必相同),避免重复计算。LRU(Least Recently Used)策略会淘汰最近最少使用的缓存项,提升性能。
语法
@functools.lru_cache(maxsize=None,typed=False)maxsize:缓存最大容量(None 表示无限制);typed:是否区分参数类型(如 1 和 1.0 视为不同键,默认 False)。
示例:优化斐波那契数列计算
importfunctoolsimporttime# 无缓存的斐波那契deffib_no_cache(n):ifn<=1:returnnreturnfib_no_cache(n-1)+fib_no_cache(n-2)# 有缓存的斐波那契@functools.lru_cache(maxsize=None)deffib_with_cache(n):ifn<=1:returnnreturnfib_with_cache(n-1)+fib_with_cache(n-2)# 性能对比start=time.time()fib_no_cache(35)print(f"无缓存耗时:{time.time()-start:.2f}s")# 约 1.5s(视机器而定)start=time.time()fib_with_cache(35)print(f"有缓存耗时:{time.time()-start:.6f}s")# 约 0.0001s# 查看缓存信息print(fib_with_cache.cache_info())# CacheInfo(hits=33, misses=36, maxsize=None, currsize=36)3. functools.partial:偏函数(固定部分参数)
作用
固定函数的部分参数(位置/关键字参数),返回一个新的可调用对象,简化后续调用(无需重复传入固定参数)。
语法
functools.partial(func,/,*args,**keywords)func:原函数;*args:要固定的位置参数;**keywords:要固定的关键字参数。
示例:简化参数调用
importfunctools# 原函数:计算 a*b + cdefcalc(a,b,c):returna*b+c# 固定 a=2(位置参数),生成新函数calc_2x=functools.partial(calc,2)print(calc_2x(3,4))# 2*3+4=10# 固定 b=5 和 c=10(混合参数)calc_5b_10c=functools.partial(calc,b=5,c=10)print(calc_5b_10c(4))# 4*5+10=30# 实用场景:二进制转整数(固定 int 的 base=2)bin_to_int=functools.partial(int,base=2)print(bin_to_int("1010"))# 104. functools.partialmethod:偏方法(类方法专用)
作用
类似partial,但专门用于类的方法(绑定/未绑定方法),无法用于普通函数。
语法
functools.partialmethod(func,/,*args,**keywords)示例:类方法参数固定
importfunctoolsclassNumberProcessor:def__init__(self,base=10):self.base=basedefconvert(self,num,target_base):"""将数字转换为指定进制"""returnint(str(num),self.base)iftarget_base==10elsebin(num)# 固定 target_base=10,生成偏方法convert_to_dec=functools.partialmethod(convert,target_base=10)# 固定 target_base=2,生成偏方法convert_to_bin=functools.partialmethod(convert,target_base=2)processor=NumberProcessor(base=16)# 初始进制为16print(processor.convert_to_dec("1A"))# 16进制1A转10进制:26print(processor.convert_to_bin(26))# 26转二进制:0b110105. functools.reduce:迭代器归约为单个值
作用
将二元函数依次应用到迭代器的元素上,最终将迭代器归约为一个值(Python 3 中reduce从内置移至 functools)。
语法
functools.reduce(function,iterable[,initializer])function:二元函数(接收两个参数,返回一个值);iterable:可迭代对象;initializer:初始值(可选,无初始值时用迭代器前两个元素启动)。
示例:聚合迭代器数据
importfunctools# 1. 累加(等价于 sum([1,2,3,4]))nums=[1,2,3,4]sum_nums=functools.reduce(lambdax,y:x+y,nums)print("累加:",sum_nums)# 10# 2. 累乘mul_nums=functools.reduce(lambdax,y:x*y,nums)print("累乘:",mul_nums)# 24# 3. 找最大值(初始值为0)max_num=functools.reduce(lambdax,y:xifx>yelsey,nums,0)print("最大值:",max_num)# 46. functools.cmp_to_key:比较函数转 key 函数
作用
将传统的比较函数(返回 -1/0/1:小于/等于/大于)转换为sorted、max等函数所需的key函数。
语法
functools.cmp_to_key(func)示例:自定义排序规则
importfunctools# 传统比较函数:按数字绝对值从大到小排序defcmp_abs(a,b):ifabs(a)>abs(b):return-1elifabs(a)<abs(b):return1else:return0nums=[-5,3,-2,8,1]# 转换为 key 函数,传入 sortedsorted_nums=sorted(nums,key=functools.cmp_to_key(cmp_abs))print("按绝对值降序:",sorted_nums)# [8, -5, 3, -2, 1]# 扩展:按字符串长度+字典序排序defcmp_str(a,b):iflen(a)>len(b):return1eliflen(a)<len(b):return-1else:return-1ifa<belse1# 长度相同则字典序升序strs=["apple","banana","pear","orange"]sorted_strs=sorted(strs,key=functools.cmp_to_key(cmp_str))print("按长度+字典序:",sorted_strs)# ['pear', 'apple', 'banana', 'orange']7. functools.total_ordering:自动补全类的比较方法
作用
装饰类后,只需定义__eq__和一个比较方法(__lt__/__le__/__gt__/__ge__),自动生成其他比较方法,避免重复代码。
语法
@functools.total_ordering示例:简化类的比较逻辑
importfunctools@functools.total_orderingclassScore:def__init__(self,value):self.value=value# 必须定义 __eq__def__eq__(self,other):ifnotisinstance(other,Score):returnNotImplementedreturnself.value==other.value# 只需定义一个比较方法(如 __lt__)def__lt__(self,other):ifnotisinstance(other,Score):returnNotImplementedreturnself.value<other.value# 自动生成 __le__、__gt__、__ge__s1=Score(80)s2=Score(90)print(s1<s2)# True(自定义)print(s1<=s2)# True(自动生成)print(s1>s2)# False(自动生成)print(s1>=s2)# False(自动生成)print(s1==s2)# False(自定义)8. functools.singledispatch:单分派泛函数
作用
实现基于第一个参数类型的函数重载(单分派:仅根据第一个参数类型匹配),替代多分支if isinstance逻辑。
语法
@functools.singledispatch# 装饰基函数deffunc(...):...@func.register(类型)# 注册指定类型的实现deffunc_类型(...):...示例:多类型数据处理
importfunctools# 基函数:处理默认类型@functools.singledispatchdefprocess_data(data):raiseNotImplementedError(f"不支持处理{type(data)}类型")# 注册 int 类型处理逻辑@process_data.register(int)def_(data):returnf"整数处理:{data*2}"# 注册 str 类型处理逻辑@process_data.register(str)def_(data):returnf"字符串处理:{data.upper()}"# 注册 list 类型处理逻辑@process_data.register(list)def_(data):returnf"列表处理:{[x*3forxindata]}"# 测试不同类型print(process_data(10))# 整数处理:20print(process_data("hello"))# 字符串处理:HELLOprint(process_data([1,2,3]))# 列表处理:[3, 6, 9]# print(process_data(dict())) # 抛出 NotImplementedError实战串联:数据处理工具集
以下案例串联所有核心 API,实现一个“日志化、可缓存、支持多类型的数据分析工具”:
importfunctoolsimporttimefromtypingimportList# ---------------------- 1. 基础装饰器(wraps) ----------------------deflog_calls(func):"""自定义装饰器:记录函数调用日志(保留元信息)"""@functools.wraps(func)defwrapper(*args,**kwargs):start=time.time()result=func(*args,**kwargs)print(f"[{time.ctime()}] 调用{func.__name__}| 耗时:{time.time()-start:.6f}s")returnresultreturnwrapper# ---------------------- 2. 数据项类(total_ordering) ----------------------@functools.total_orderingclassDataItem:def__init__(self,name:str,value:int):self.name=name self.value=valuedef__eq__(self,other):returnisinstance(other,DataItem)andself.value==other.valuedef__lt__(self,other):returnisinstance(other,DataItem)andself.value<other.valuedef__repr__(self):returnf"DataItem({self.name},{self.value})"# ---------------------- 3. 多类型数据解析(singledispatch) ----------------------@functools.singledispatch@log_calls# 结合日志装饰器defparse_data(raw_data)->List[DataItem]:"""解析原始数据为 DataItem 列表(多类型支持)"""raiseValueError(f"不支持的原始数据类型:{type(raw_data)}")@parse_data.register(str)@functools.lru_cache(maxsize=10)# 4. 缓存字符串解析结果(lru_cache)def_(raw_data:str,sep:str=",")->List[DataItem]:"""解析字符串格式数据(如 "a:1,b:2")"""items=[]forpairinraw_data.split(sep):name,value=pair.split(":")items.append(DataItem(name,int(value)))returnitems@parse_data.register(list)def_(raw_data:list)->List[DataItem]:"""解析列表格式数据(如 [("a",1), ("b",2)])"""return[DataItem(name,value)forname,valueinraw_data]# ---------------------- 5. 偏函数:固定分隔符(partial) ----------------------parse_csv_data=functools.partial(parse_data,sep=";")# 固定分隔符为;# ---------------------- 6. 数据聚合:求和/最大值(reduce) ----------------------@log_callsdefaggregate_data(items:List[DataItem],agg_type:str="sum")->int:"""聚合 DataItem 的 value(sum/max)"""values=[item.valueforiteminitems]ifagg_type=="sum":returnfunctools.reduce(lambdax,y:x+y,values,0)elifagg_type=="max":returnfunctools.reduce(lambdax,y:xifx>yelsey,values,0)else:raiseValueError(f"不支持的聚合类型:{agg_type}")# ---------------------- 7. 自定义排序:按名称长度(cmp_to_key) ----------------------defcmp_name_length(a:DataItem,b:DataItem)->int:"""比较函数:按名称长度升序,长度相同按值降序"""iflen(a.name)>len(b.name):return1eliflen(a.name)<len(b.name):return-1else:return-1ifa.value>b.valueelse1@log_callsdefsort_items(items:List[DataItem])->List[DataItem]:"""按名称长度排序 DataItem 列表"""returnsorted(items,key=functools.cmp_to_key(cmp_name_length))# ---------------------- 8. 类方法偏函数(partialmethod) ----------------------classDataProcessor:def__init__(self,default_agg:str="sum"):self.default_agg=default_aggdefaggregate(self,items:List[DataItem],agg_type:str):returnaggregate_data(items,agg_type)# 固定 agg_type 为默认值(partialmethod)aggregate_default=functools.partialmethod(aggregate,agg_type=self.default_agg)# ====================== 实战调用 ======================if__name__=="__main__":# 1. 解析字符串数据(缓存+日志)raw_str="apple:5,banana:3,pear:8"items1=parse_data(raw_str)items2=parse_data(raw_str)# 命中缓存,无重复解析print("解析结果:",items1)# 2. 解析CSV格式数据(偏函数)raw_csv="grape:2;orange:7;mango:4"csv_items=parse_csv_data(raw_csv)print("CSV解析结果:",csv_items)# 3. 排序(cmp_to_key)sorted_items=sort_items(items1)print("排序结果:",sorted_items)# 4. 聚合(reduce)sum_value=aggregate_data(items1,"sum")max_value=aggregate_data(items1,"max")print("求和:",sum_value," 最大值:",max_value)# 5. 类方法偏函数processor=DataProcessor()default_agg=processor.aggregate_default(items1)print("默认聚合(sum):",default_agg)# 6. 数据项比较(total_ordering)print("数据项比较:",DataItem("a",5)<DataItem("b",8))输出结果(核心片段)
[Thu Dec 11 10:00:00 2025] 调用 parse_data | 耗时:0.000012s 解析结果: [DataItem(apple, 5), DataItem(banana, 3), DataItem(pear, 8)] [Thu Dec 11 10:00:00 2025] 调用 parse_data | 耗时:0.000001s # 命中缓存 [Thu Dec 11 10:00:00 2025] 调用 parse_data | 耗时:0.000008s CSV解析结果: [DataItem(grape, 2), DataItem(orange, 7), DataItem(mango, 4)] [Thu Dec 11 10:00:00 2025] 调用 sort_items | 耗时:0.000005s 排序结果: [DataItem(pear, 8), DataItem(apple, 5), DataItem(banana, 3)] [Thu Dec 11 10:00:00 2025] 调用 aggregate_data | 耗时:0.000003s [Thu Dec 11 10:00:00 2025] 调用 aggregate_data | 耗时:0.000001s 求和: 16 最大值: 8 [Thu Dec 11 10:00:00 2025] 调用 aggregate_data | 耗时:0.000001s 默认聚合(sum): 16 数据项比较: True总结
functools 核心价值是简化函数/可调用对象的封装与扩展,各 API 适用场景如下:
| API | 核心场景 | 注意事项 |
|---|---|---|
| wraps | 装饰器保留原函数元信息 | 必须装饰装饰器的内层函数 |
| lru_cache | 纯函数结果缓存 | 参数必须可哈希(如不能是列表/字典) |
| partial/partialmethod | 固定函数/方法参数 | partial 用于普通函数,partialmethod 用于类方法 |
| reduce | 迭代器聚合为单个值 | 避免嵌套过深,简单聚合可用 sum/max 等内置函数 |
| cmp_to_key | 自定义排序规则 | 仅用于 sorted/max/min 等需 key 函数的场景 |
| total_ordering | 类的比较方法简化 | 必须定义eq和至少一个比较方法 |
| singledispatch | 按参数类型重载函数 | 仅支持第一个参数类型匹配(多分派需第三方库) |
掌握这些 API 可大幅减少重复代码,提升函数封装的规范性和性能,是 Python 进阶开发的必备工具。