
一、functool库的用途:操作其他函数的函数;该模块提供了许多改写或拓展函数或其他可调用对象的工具,而无需完全重写它们
二、功能
1、functools.lru_cache是一个函数缓存装饰器,lru说明了算法,Least Recently Used,这个算法被用在很多地方,比如OS做虚拟内存置换的时候。LRU算法的思想:将最近最少使用到的区域,进行某个动作,比如置换到硬盘,或者从缓存中删除。
>>> import functools>>> @functools.lru_cache... def test(a,b):... print('i am in test')... return a+b...>>> test(1,1)i am in test2>>> test(1,1) # i an in test 被缓存了?2>>> test(2,2)i am in test4>>> test(2,2)4>>> test.cache_info()CacheInfo(hits=2, misses=2, maxsize=128, currsize=2)>>> test.cache_clear()>>> test(1,1)i am in test2>>> test(2,2)i am in test4>>> test.cache_info()CacheInfo(hits=0, misses=2, maxsize=128, currsize=2)
2、偏对象partial
以下例子展示了 myfunc() 函数的两个 partial 对象。show_detail() 函数输出一个偏对象的 func、args 与 keywords 属性。
functools_partial.py
import functoolsdef myfunc(a, b=2):"myfunc() 的文档字符串"print(' 调用 myfunc 的参数:', (a, b))def show_details(name, f, is_partial=False):"显示一个可调用对象的细节"print('{}:'.format(name))print(' 对象:', f)if not is_partial:print(' __name__:', f.__name__)if is_partial:print(' func:', f.func)print(' args:', f.args)print(' keywords:', f.keywords)returnshow_details('myfunc', myfunc)myfunc('a', 3)print()# 为参数 b 设置另一个默认值# 但要求调用者提供参数 ap1 = functools.partial(myfunc, b=4)show_details('默认关键词参数的偏对象', p1, True)p1('传递参数 a')p1('覆盖参数 b', b=5)print()# 为参数 a 与 b 都设置默认值p2 = functools.partial(myfunc, '默认的参数 a', b=99)show_details('多个默认值的偏对象', p2, True)p2()p2(b='覆盖参数 b')print()print('参数不足:')p1()
在这个例子的最后一行,第一个 partial 对象 p1 被调用时若不传递参数 a,将导致程序异常。
$ python3 functools_partial.pymyfunc:对象: <function myfunc at 0x1007a6a60>__name__: myfunc调用 myfunc 的参数:('a', 3)默认关键词参数的偏对象:对象:functools.partial(<function myfunc at 0x1007a6a60>,b=4)func: <function myfunc at 0x1007a6a60>args: ()keywords: {'b': 4}调用 myfunc 的参数:('传递参数 a', 4)调用 myfunc 的参数:('覆盖参数 b', 5)多个默认值的偏对象:对象:functools.partial(<function myfunc at 0x1007a6a60>,'默认的参数 a', b=99)func: <function myfunc at 0x1007a6a60>args: ('默认的参数 a',)keywords: {'b': 99}调用 myfunc 的参数:('默认的参数 a', 99)调用 myfunc 的参数:('默认的参数 a', '覆盖参数 b')参数不足:Traceback (most recent call last):File "functools_partial.py", line 51, in <module>p1()TypeError: myfunc() missing 1 required positional argument: 'a'# 译者注:该处错误信息为:“类型错误:myfunc() 缺少位置参数:‘a’”
获取方法属性
partial 对象默认并没有 __name__ 或者 __doc__ 属性,如果没有这些属性,装饰功能就更难以调试。我们可以使用 update_wrapper() 方法将原方法的属性复制或添加新属性到 partial 对象。
functools_update_wrapper.pyimport functoolsdef myfunc(a, b=2):"Docstring for myfunc()."print(' called myfunc with:', (a, b))def show_details(name, f):"Show details of a callable object."print('{}:'.format(name))print(' object:', f)print(' __name__:', end=' ')try:print(f.__name__)except AttributeError:print('(no __name__)')print(' __doc__', repr(f.__doc__))print()show_details('myfunc', myfunc)p1 = functools.partial(myfunc, b=4)show_details('raw wrapper', p1)print('Updating wrapper:')print(' assign:', functools.WRAPPER_ASSIGNMENTS)print(' update:', functools.WRAPPER_UPDATES)print()functools.update_wrapper(p1, myfunc)show_details('updated wrapper', p1)
需要添加到装饰器里面的属性在 WRAPPER_ASSIGNMENTS 中定义, 而 WRAPPER_UPDATES 列出需要修改的值。
$ python3 functools_update_wrapper.pymyfunc:object: <function myfunc at 0x1018a6a60>__name__: myfunc__doc__ 'Docstring for myfunc().'raw wrapper:object: functools.partial(<function myfunc at 0x1018a6a60>,b=4)__name__: (no __name__)__doc__ 'partial(func, *args, **keywords) - new function withpartial application\n of the given arguments and keywords.\n'Updating wrapper:assign: ('__module__', '__name__', '__qualname__', '__doc__','__annotations__')update: ('__dict__',)updated wrapper:object: functools.partial(<function myfunc at 0x1018a6a60>,b=4)__name__: myfunc__doc__ 'Docstring for myfunc().'
其他可调用对象
偏函数不仅仅可用于独立函数,它可与任何可调用对象一起工作。
functools_callable.pyimport functoolsclass MyClass:"Demonstration class for functools"def __call__(self, e, f=6):"Docstring for MyClass.__call__"print(' called object with:', (self, e, f)def show_details(name, f):"Show details of a callable object."print('{}:'.format(name))print(' object:', f)print(' __name__:', end=' ')try:print(f.__name__)except AttributeError:print('(no __name__)')print(' __doc__', repr(f.__doc__))returno = MyClass()show_details('instance', o)o('e goes here')print()p = functools.partial(o, e='default for e', f=8)functools.update_wrapper(p, o)show_details('instance wrapper', p)p()
这个例子用带有 __call__() 方法的类的实例来生成偏函数。
$ python3 functools_callable.pyinstance:object: <__main__.MyClass object at 0x1011b1cf8>__name__: (no __name__)__doc__ 'Demonstration class for functools'called object with: (<__main__.MyClass object at 0x1011b1cf8>,'e goes here', 6)instance wrapper:object: functools.partial(<__main__.MyClass object at0x1011b1cf8>, f=8, e='default for e')__name__: (no __name__)__doc__ 'Demonstration class for functools'called object with: (<__main__.MyClass object at 0x1011b1cf8>,'default for e', 8)
方法和函数
partial() 返回的是一个可调用对象,可以直接使用;而 partialmethod() 返回的是一个可调用方法,可以用作一个对象的非绑定方法。在下例中,将相同的独立函数作为 MyClass 的属性添加了两次,一次是使用 partialmethod() 作为 method1() ,另一次是使用 partial() 作为 method2() 。
functools_partialmethod.pyimport functoolsdef standalone(self, a=1, b=2):"独立函数"print(' called standalone with:', (self, a, b))if self is not None:print(' self.attr =', self.attr)class MyClass:"工具箱( functools )的示范类"def __init__(self):self.attr = 'instance attribute'method1 = functools.partialmethod(standalone)method2 = functools.partial(standalone)o = MyClass()print('standalone')standalone(None)print()print('method1 as partialmethod')o.method1()print()print('method2 as partial')try:o.method2()except TypeError as err:print('ERROR: {}'.format(err))
method1() 可以从 MyClass 的实例中调用,该实例作为第一个参数传递,就像通常定义的方法一样。method2() 没有被设置为绑定方法,所以必须显示地传递 self 参数,否则在调用时将会导致 TypeError 。
$ python3 functools_partialmethod.pystandalonecalled standalone with: (None, 1, 2)method1 as partialmethodcalled standalone with: (<__main__.MyClass object at0x1007b1d30>, 1, 2)self.attr = instance attributemethod2 as partialERROR: standalone() missing 1 required positional argument:'self'
获得装饰器( Decorator )的函数属性
在装饰器中更新已封装好的、可调用的属性非常有用,因为转换后的函数会具有原始「裸」函数的属性。
functools_wraps.pyimport functoolsdef show_details(name, f):"显示可调用对象的细节。"print('{}:'.format(name))print(' object:', f)print(' __name__:', end=' ')try:print(f.__name__)except AttributeError:print('(no __name__)')print(' __doc__', repr(f.__doc__))print()def simple_decorator(f):@functools.wraps(f)def decorated(a='decorated defaults', b=1):print(' decorated:', (a, b))print(' ', end=' ')return f(a, b=b)return decorateddef myfunc(a, b=2):" myfunc() 并不复杂"print(' myfunc:', (a, b))return# 原始函数show_details('myfunc', myfunc)myfunc('unwrapped, default b')myfunc('unwrapped, passing b', 3)print()# 显式封装wrapped_myfunc = simple_decorator(myfunc)show_details('wrapped_myfunc', wrapped_myfunc)wrapped_myfunc()wrapped_myfunc('args to wrapped', 4)print()# 用装饰器语法封装@simple_decoratordef decorated_myfunc(a, b):myfunc(a, b)returnshow_details('decorated_myfunc', decorated_myfunc)decorated_myfunc()decorated_myfunc('args to decorated', 4)
functools 提供了一个装饰器, wraps() ,它将 update_wrapper() 装饰到了函数中。
$ python3 functools_wraps.pymyfunc:object: <function myfunc at 0x101241b70>__name__: myfunc__doc__ 'myfunc() is not complicatemyfunc: ('unwrapped, default b', 2)myfunc: ('unwrapped, passing b', 3)wrapped_myfunc:object: <function myfunc at 0x1012e62f0>__name__: myfunc__doc__ 'myfunc() is not complicated'decorated: ('decorated defaults', 1)myfunc: ('decorated defaults', 1)decorated: ('args to wrapped', 4)myfunc: ('args to wrapped', 4)decorated_myfunc:object: <function decorated_myfunc at 0x1012e6400>__name__: decorated_myfunc__doc__ Nonedecorated: ('decorated defaults', 1)myfunc: ('decorated defaults', 1)decorated: ('args to decorated', 4)myfunc: ('args to decorated', 4)
对比
Python 2 中,每一个类都可以定义 __cmp__() 方法,让其返回 -1,0 或者 1 来知道这个对象是小于,等于还是大于某个其他对象。从 Python 2.1 开始,则有了更细粒度的对比方法 API (__lt__(), __le__(), __eq__(), __ne__(), __gt__(), 和 __ge__()),每一个都只提供一种对比操作并且返回布尔类型的值。在 Python 3 中, __cmp__() 方法已不再赞成使用,其他更细粒度的方法更适合实现进行这些操作, 同时 functools 提供的工具也可以更好得进行这些操作的执行。
更细粒度的对比方法
更细粒度的对比方法被设计用来让类实现复杂的比较,以让每种测试都尽可能有效完成。不过,对于类来说,何时何地进行比较相对来说要容易知道,我们不需要将每个对比方法都实现。total_ordering() 类装饰器为类提供了一些方法可以为我们自动添加其余我们没有添加的对比方法。
functools_total_ordering.pyimport functoolsimport inspectfrom pprint import pprint@functools.total_orderingclass MyObject:def __init__(self, val):self.val = valdef __eq__(self, other):print(' testing __eq__({}, {})'.format(self.val, other.val))return self.val == other.valdef __gt__(self, other):print(' testing __gt__({}, {})'.format(self.val, other.val))return self.val > other.valprint('Methods:\n')pprint(inspect.getmembers(MyObject, inspect.isfunction))a = MyObject(1)b = MyObject(2)print('\nComparisons:')for expr in ['a < b', 'a <= b', 'a == b', 'a >= b', 'a > b']:print('\n{:<6}:'.format(expr))result = eval(expr)print(' result of {}: {}'.format(expr, result))
要想让 total_ordering() 装饰器工作,必须实现类的 __eq__() 和其他对比方法中的一个。装饰器会将其余的方法按照提供的对比方法进行自动实现。如果自动实现的某方法无法进行比较,则会返回 NotImplemented ,这样我们可以获取到它,然后使用另一个对象的对比操作。
(译注:NotImplemented 并不是一个异常,它存在的意义是在进行一些排序操作时,出现了问题也不打破操作,比如如果 a.__gt__(b) 返回了 NotImplemented 则进行 b.__lt__(a) 的操作,这样可以在报错前尝试挽回一下。
不实现 __eq__() 其实也可以运行,只不过 == 和 != 是没有结果的。
)
$ python3 functools_total_ordering.pyMethods:[('__eq__', <function MyObject.__eq__ at 0x10139a488>),('__ge__', <function _ge_from_gt at 0x1012e2510>),('__gt__', <function MyObject.__gt__ at 0x10139a510>),('__init__', <function MyObject.__init__ at 0x10139a400>),('__le__', <function _le_from_gt at 0x1012e2598>),('__lt__', <function _lt_from_gt at 0x1012e2488>)]Comparisons:a < b :testing __gt__(1, 2)testing __eq__(1, 2)result of a < b: Truea <= b:testing __gt__(1, 2)result of a <= b: Truea == b:testing __eq__(1, 2)result of a == b: Falsea >= b:testing __gt__(1, 2)testing __eq__(1, 2)result of a >= b: Falsea > b :testing __gt__(1, 2)result of a > b: False
顺序整理
因为 Python 3 对旧式对比函数已不在赞成使用, 诸如 sort() 中的 cmp 参数也不再支持。对于已经使用了旧式对比方法的程序,可以使用 cmp_to_key() 把它们转换成一个会返回 整理 key 的函数,整理 key 就是排序时决定该数据所在位置的 key 。
functools_cmp_to_key.pyimport functoolsclass MyObject:def __init__(self, val):self.val = valdef __str__(self):return 'MyObject({})'.format(self.val)def compare_obj(a, b):"""旧式对比函数."""print('comparing {} and {}'.format(a, b))if a.val < b.val:return -1elif a.val > b.val:return 1return 0
# 使用 cmp_to_key() 转换为一个返回 key 的函数get_key = functools.cmp_to_key(compare_obj)def get_key_wrapper(o):"这个函数用于打印 get_key 的一些信息。"new_key = get_key(o)print('key_wrapper({}) -> {!r}'.format(o, new_key))return new_keyobjs = [MyObject(x) for x in range(5, 0, -1)]for o in sorted(objs, key=get_key_wrapper):print(o)
正常情况下 cmp_to_key() 直接使用就行,例子中的额外的包装函数用于打印一些 key 函数的信息来方便了解内容。
输出的内容是 sorted() 每次调用 get_key_wrapper() 来将序列中的数据转换为 key 的过程。key 则是 cmp_to_key() 所返回的, functools
的源码中是实现了一个具备更细粒度对比 API 的类来完成这个操作的。所有的 key 创建完成后,就会用创建好的 key 来为原序列排序。
$ python3 functools_cmp_to_key.pykey_wrapper(MyObject(5)) -> <functools.KeyWrapper object at0x1011c5530>key_wrapper(MyObject(4)) -> <functools.KeyWrapper object at0x1011c5510>key_wrapper(MyObject(3)) -> <functools.KeyWrapper object at0x1011c54f0>key_wrapper(MyObject(2)) -> <functools.KeyWrapper object at0x1011c5390>key_wrapper(MyObject(1)) -> <functools.KeyWrapper object at0x1011c5710>comparing MyObject(4) and MyObject(5)comparing MyObject(3) and MyObject(4)comparing MyObject(2) and MyObject(3)comparing MyObject(1) and MyObject(2)MyObject(1)MyObject(2)MyObject(3)MyObject(4)MyObject(5)
缓存
lru_cache() 装饰器会让某函数具有最近最小缓存机制。所有传递过来的参数都会被哈希化,用于后续结果的映射。之后再次调用相同的参数时会从缓存中直接调取出结果而不再经过函数运算。同时此装饰器还给原函数加了一个用于检测缓存状态的方法(cache_info())和一个清空缓存的方法(cache_clear())。
functools_lru_cache.pyimport functools@functools.lru_cache()def expensive(a, b):print('expensive({}, {})'.format(a, b))return a * bMAX = 2print('First set of calls:')for i in range(MAX):for j in range(MAX):expensive(i, j)print(expensive.cache_info())print('\nSecond set of calls:')for i in range(MAX + 1):for j in range(MAX + 1):expensive(i, j)print(expensive.cache_info())print('\nClearing cache:')expensive.cache_clear()print(expensive.cache_info())print('\nThird set of calls:')for i in range(MAX):for j in range(MAX):expensive(i, j)print(expensive.cache_info())
例子中在嵌套循环中调用了几次 expensive() 。第二次调用时相同的参数生成的值就是直接显示了缓存中的值。当缓存清空时,再次调用这些参数时就又重新计算了。
$ python3 functools_lru_cache.pyFirst set of calls:expensive(0, 0)expensive(0, 1)expensive(1, 0)expensive(1, 1)CacheInfo(hits=0, misses=4, maxsize=128, currsize=4)Second set of calls:expensive(0, 2)expensive(1, 2)expensive(2, 0)expensive(2, 1)expensive(2, 2)CacheInfo(hits=4, misses=9, maxsize=128, currsize=9)Clearing cache:CacheInfo(hits=0, misses=0, maxsize=128, currsize=0)Third set of calls:expensive(0, 0)expensive(0, 1)expensive(1, 0)expensive(1, 1)CacheInfo(hits=0, misses=4, maxsize=128, currsize=4)
为防止缓存在长时间运行的程序中无限增长,它还贴心的提供了一个最大缓存值。默认最大值为 128,不过可以用 maxsize 参数来修改它。
functools_lru_cache_expire.pyimport functools@functools.lru_cache(maxsize=2)def expensive(a, b):print('called expensive({}, {})'.format(a, b))return a * bdef make_call(a, b):print('({}, {})'.format(a, b), end=' ')pre_hits = expensive.cache_info().hitsexpensive(a, b)post_hits = expensive.cache_info().hitsif post_hits > pre_hits:print('cache hit')print('Establish the cache')make_call(1, 2)make_call(2, 3)print('\nUse cached items')make_call(1, 2)make_call(2, 3)print('\nCompute a new value, triggering cache expiration')make_call(3, 4)print('\nCache still contains one old item')make_call(2, 3)print('\nOldest item needs to be recomputed')make_call(1, 2)
本例中将缓存大小设置为 2。第三次使用独立的一组参数(3,4)时,缓存中最没有被访问过的就会被丢弃并替换为这个新值。
$ python3 functools_lru_cache_expire.pyEstablish the cache(1, 2) called expensive(1, 2)(2, 3) called expensive(2, 3)Use cached items(1, 2) cache hit(2, 3) cache hitCompute a new value, triggering cache expiration(3, 4) called expensive(3, 4)Cache still contains one old item(2, 3) cache hitOldest item needs to be recomputed(1, 2) called expensive(1, 2)
lru_cache() 所管理的缓存的 key 必须是可哈希化的对象,所以被其装饰的函数的参数也必须是可哈希化的才行。
functools_lru_cache_arguments.pyimport functools@functools.lru_cache(maxsize=2)def expensive(a, b):print('called expensive({}, {})'.format(a, b))return a * bdef make_call(a, b):print('({}, {})'.format(a, b), end=' ')pre_hits = expensive.cache_info().hitsexpensive(a, b)post_hits = expensive.cache_info().hitsif post_hits > pre_hits:print('cache hit')make_call(1, 2)try:make_call([1], 2)except TypeError as err:print('ERROR: {}'.format(err))try:make_call(1, {'2': 'two'})except TypeError as err:print('ERROR: {}'.format(err))
如果函数传递过来的对象不能被哈希化,就会抛出一个 TypeError 异常。
$ python3 functools_lru_cache_arguments.py(1, 2) called expensive(1, 2)([1], 2) ERROR: unhashable type: 'list'(1, {'2': 'two'}) ERROR: unhashable type: 'dict'
生成数据集合
reduce() 函数可接受一个可调用的数据序列,并且基于序列中的值不断经由一个可调用的对象处理,最后生成一个总值。
functools_reduce.pyimport functoolsdef do_reduce(a, b):print('do_reduce({}, {})'.format(a, b))return a + bdata = range(1, 5)print(data)result = functools.reduce(do_reduce, data)print('result: {}'.format(result))
本例是将所输入的序列里的数字加起来。
$ python3 functools_reduce.pyrange(1, 5)do_reduce(1, 2)do_reduce(3, 3)do_reduce(6, 4)result: 10
可选参数 initializer 可以放在处理序列里的值之前然后一起进行处理。可以在已经处理过的序列需要和新的序列进行计算时使用。
functools_reduce_initializer.pyimport functoolsdef do_reduce(a, b):print('do_reduce({}, {})'.format(a, b))return a + bdata = range(1, 5)print(data)result = functools.reduce(do_reduce, data, 99)print('result: {}'.format(result))
本例使用 99 作为 reduce() 的初始值参与计算,以展示效果。
$ python3 functools_reduce_initializer.pyrange(1, 5)do_reduce(99, 1)do_reduce(100, 2)do_reduce(102, 3)do_reduce(105, 4)result: 109
没有初始值时,只有一个数据的序列会直接返回。空列表的话会产生一个错误,不过有初始值的话就会返回那个初始值。
functools_reduce_short_sequences.pyimport functoolsdef do_reduce(a, b):print('do_reduce({}, {})'.format(a, b))return a + bprint('Single item in sequence:',functools.reduce(do_reduce, [1]))print('Single item in sequence with initializer:',functools.reduce(do_reduce, [1], 99))print('Empty sequence with initializer:',functools.reduce(do_reduce, [], 99))try:print('Empty sequence:', functools.reduce(do_reduce, []))except TypeError as err:print('ERROR: {}'.format(err))
初始值参数会被作为默认值参与与序列里的值的计算,请小心使用。如果搞不清是否该使用它,抛出个 TypeError 也不失是个好的办法。
$ python3 functools_reduce_short_sequences.pySingle item in sequence: 1do_reduce(99, 1)Single item in sequence with initializer: 100Empty sequence with initializer: 99ERROR: reduce() of empty sequence with no initial value
泛型函数
在动态类型语言(如 Python)中,经常有在执行时需要辨别不同类型的参数的需求,比如要处理的是一个列表里的数据还是一个单个的数据。直接检测参数的类型当然简单,但不同的功能也可以写到不同的函数中,所以 functools 提供了 singledispatch() 装饰器来让我们注册 泛型函数 以自动基于类型进行切换。
functools_singledispatch.pyimport functools@functools.singledispatchdef myfunc(arg):print('default myfunc({!r})'.format(arg))@myfunc.register(int)def myfunc_int(arg):print('myfunc_int({})'.format(arg))@myfunc.register(list)def myfunc_list(arg):print('myfunc_list()')for item in arg:print(' {}'.format(item))myfunc('string argument')myfunc(1)myfunc(2.3)myfunc(['a', 'b', 'c'])
新函数的 register() 属性用于作为装饰器注册其他功能。第一个被 singledispatch() 装饰的函数作为默认实现,比如如果发现没有被注册的类型输入时,就只有它起作用,例子中以 float 类型做了测试。
$ python3 functools_singledispatch.pydefault myfunc('string argument')myfunc_int(1)default myfunc(2.3)myfunc_list()abc
没有发现精确地类型匹配时,会根据继承顺序来寻找最相近的类型。
functools_singledispatch_mro.pyimport functoolsclass A:passclass B(A):passclass C(A):passclass D(B):passclass E(C, D):pass@functools.singledispatchdef myfunc(arg):print('default myfunc({})'.format(arg.__class__.__name__))@myfunc.register(A)def myfunc_A(arg):print('myfunc_A({})'.format(arg.__class__.__name__))@myfunc.register(B)def myfunc_B(arg):print('myfunc_B({})'.format(arg.__class__.__name__))@myfunc.register(C)def myfunc_C(arg):print('myfunc_C({})'.format(arg.__class__.__name__))myfunc(A())myfunc(B())myfunc(C())myfunc(D())myfunc(E())
本例中,类 D 和 E 都没有在通用函数中注册,所以就根据类的继承来确定具体如何执行。
$ python3 functools_singledispatch_mro.pymyfunc_A(A)myfunc_B(B)myfunc_C(C)myfunc_B(D)myfunc_C(E)




