本文最后更新于:2024年5月7日 下午
介绍Python 中内置库 functools —— 可调用对象上的高阶函数和操作 。
简介
Functools 模块用于高阶函数: 作用于或返回其他函数的函数。一般来说,任何可调用对象都可以作为此模块的函数处理。
functools 包含一下模块:
functools.cache
functools.cached_property
functools.cmp_to_key
functools.lru_cache
functools.total_ordering
functools.partial
functools.partialmethod
functools.reduce
functools.singledispatch
functools.singledispatchmethod
functools.update_wrapper
functools.wraps
cache
New in version 3.9.
1 @functools .cache (user_function)
为函数返回结果与输入值创建字典,再次访问时则直接调用结果,缓存没有数量限制。不会删除旧的数据也不会限制内存尺寸,会比 lru_cache(maxsize=size)
轻量且快速。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import functools@functools.cache def feb (n ): print ('calling n: ' , n) return feb(n-1 ) + feb(n-2 ) if n > 2 else 1 print (feb(8 ))print (feb(4 ))print (feb(10 )) --> calling n: 8 calling n: 7 calling n: 6 calling n: 5 calling n: 4 calling n: 3 calling n: 2 calling n: 1 21 3 calling n: 10 calling n: 9 55
计算过的值就不会重复计算了。
cached_property
New in version 3.8.
1 @functools .cached_property(func )
将类的方法转换为一个属性,该属性的值只计算一次,然后作为实例生命周期的常规属性缓存。
1 2 3 4 5 6 7 8 9 10 import functoolsclass DataSet : def __init__ (self, sequence_of_numbers ): self._data = tuple (sequence_of_numbers) @functools.cached_property def stdev (self ): return statistics.stdev(self._data)
cmp_to_key
New in version 3.2.
1 functools.cmp_to_key(func )
将旧式比较函数转换为关键函数。与接受关键函数的工具一起使用(例如 sort ()、 min ()、 max ()、 heapq.nbest ()、 heapq.nbest ()、 itertools.groupby ())。该函数主要用作从 Python 2转换的程序的转换工具,Python 2支持使用比较函数。
1 sorted (iterable, key=cmp_to_key(locale.strcoll))
lru_cache
1 2 @functools .lru_cache (user_function)@functools .lru_cache (maxsize=128 , typed=False)
修饰符用制表调用包装函数,该调用最多可以保存最新调用的最大值。当使用相同的参数周期性地调用一个昂贵的或 I/O 绑定的函数时,它可以节省时间。
缓存是线程安全的,因此可以在多个线程中使用包装函数。这意味着在并发更新期间,底层数据结构将保持一致。
不同的参数模式可以被认为是具有不同缓存条目的不同调用。例如,f (a = 1,b = 2)和 f (b = 2,a = 1)的关键字参数顺序不同,可能有两个单独的缓存条目。
如果类型化设置为 true,则将分别缓存不同类型的函数参数。如果类型为 false,则实现通常将它们视为等效调用,并且只缓存一个结果。(有些类型,例如 str 和 int,即使类型为 false,也可以单独缓存。)
1 2 3 4 5 6 7 8 9 @lru_cache(maxsize=32 ) def get_pep (num ): 'Retrieve text of a Python Enhancement Proposal' resource = 'https://peps.python.org/pep-%04d/' % num try : with urllib.request.urlopen(resource) as s: return s.read() except urllib.error.HTTPError: return 'Not Found'
total_ordering
New in version 3.2.
1 @functools .total_ordering
提供简便的方式定义类比较方法,仅需定义 __lt__()
, __le__()
, __gt__()
, or __ge__()
其中之一和 __eq__()
方法即可完成完备的比较定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @total_ordering class Student : def _is_valid_operand (self, other ): return (hasattr (other, "lastname" ) and hasattr (other, "firstname" )) def __eq__ (self, other ): if not self._is_valid_operand(other): return NotImplemented return ((self.lastname.lower(), self.firstname.lower()) == (other.lastname.lower(), other.firstname.lower())) def __lt__ (self, other ): if not self._is_valid_operand(other): return NotImplemented return ((self.lastname.lower(), self.firstname.lower()) < (other.lastname.lower(), other.firstname.lower()))
虽然这个装饰符使得创建行为良好的完全有序的类型变得很容易,但它的代价是执行速度较慢,派生比较方法的堆栈跟踪更复杂。如果性能基准测试表明这是给定应用程序的瓶颈,那么实现所有六种富比较方法可能会提供一个简单的速度提升。
partial
将函数输入以写死的方式减少个数,返回新的函数接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import functoolsdef show (A, B, C='test' ): print ("A: " , A) print ("B: " , B) print ("C: " , C) new_show = functools.partial(show, 'ABC' , C='partial input' ) new_show(3 )pass --> A: ABC B: 4 C: partial input
partialmethod
New in version 3.4.
返回一个新的 partial 方法描述符,它的行为类似于 partial,只不过它被设计用作方法定义,而不是直接调用。
相当于该方法修改了原始函数,而不是生成一个输入参数更少的新函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Cell : def __init__ (self ): self._alive = False @property def alive (self ): return self._alive def set_state (self, state ): self._alive = bool (state) set_alive = partialmethod(set_state, True ) set_dead = partialmethod(set_state, False ) c = Cell() c.alive -->False c.set_alive() c.alive -->True
reduce
1 functools.reduce (function , iterable[, initializer])
迭代计算函数,从左到右计算,将结果放到最左边,直接进行下一次计算。
reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])
等价于 ((((1+2)+3)+4)+5)
1 reduce(lambda x, y: x+y, [1 , 2 , 3 , 4 , 5 ])
singledispatch
New in version 3.4.
将函数转换为单分派通用函数。 可以根据输入数据类型不同调用不同的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 from functools import singledispatch@singledispatch def fun (arg, verbose=False ): if verbose: print ("Let me just say," , end=" " ) print (arg)@fun.register def _ (arg: int , verbose=False ): if verbose: print ("Strength in numbers, eh?" , end=" " ) print (arg)@fun.register def _ (arg: list , verbose=False ): if verbose: print ("Enumerate this:" ) for i, elem in enumerate (arg): print (i, elem)@fun.register def _ (arg: int | float , verbose=False ): if verbose: print ("Strength in numbers, eh?" , end=" " ) print (arg)from typing import Union @fun.register def _ (arg: Union [list , set ], verbose=False ): if verbose: print ("Enumerate this:" ) for i, elem in enumerate (arg): print (i, elem)@fun.register(float ) @fun.register(Decimal ) def fun_num (arg, verbose=False ): if verbose: print ("Half of your number:" , end=" " ) print (arg / 2 ) fun_num is fun
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 fun("Hello, world." ) Hello, world. fun("test." , verbose=True ) Let me just say, test. fun(42 , verbose=True ) Strength in numbers, eh? 42 fun(['spam' , 'spam' , 'eggs' , 'spam' ], verbose=True ) Enumerate this: 0 spam 1 spam 2 eggs 3 spam fun(None ) Nothing. fun(1.23 ) 0.615
singledispatchmethod
New in version 3.8.
要为类定义一个泛型方法,可以使用 @singlepatchmethod
装饰符对其进行装饰。当使用 @singlepatchmethod
定义函数时,请注意调度发生在第一个非 self 或 non-cls 参数的类型上:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Negator : @singledispatchmethod def neg (self, arg ): raise NotImplementedError("Cannot negate a" ) @neg.register def _ (self, arg: int ): return -arg @neg.register def _ (self, arg: bool ): return not arg
@singlepatchmethod
支持与其他修饰符(如@classmethod)嵌套。注意,为了支持 patcher.register,singlepatchmethod 必须是最外层的装饰器。下面是 Negator 类,其中的 neg 方法绑定到该类,而不是该类的实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Negator : @singledispatchmethod @classmethod def neg (cls, arg ): raise NotImplementedError("Cannot negate a" ) @neg.register @classmethod def _ (cls, arg: int ): return -arg @neg.register @classmethod def _ (cls, arg: bool ): return not arg
update_wrapper
New in version 3.2
1 functools.update_wrapper(wrapper , wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)
更新一个包装函式,使其看起来像包装好的函数。可选参数是元组,用于指定原始函数的哪些属性被直接分配给包装函式上的匹配属性,以及哪些包装函式属性被更新为原始函数的相应属性。
wraps
1 @functools.wraps(wrapped, assigned =WRAPPER_ASSIGNMENTS, updated =WRAPPER_UPDATES)
这是一个简写函数,用于在定义包装函式时调用 update_wrapper()
作为函数修饰符。它等效于partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from functools import wrapsdef my_decorator (f ): @wraps(f ) def wrapper (*args, **kwds ): print ('Calling decorated function' ) return f(*args, **kwds) return wrapper@my_decorator def example (): """Docstring""" print ('Called example function' ) --> example() Calling decorated function Called example function example.__name__ 'example' example.__doc__ 'Docstring'
参考资料
文章链接:
https://www.zywvvd.com/notes/coding/python/python-functools/python-functools/