functools——Tools for Manipulating Functions

it2024-11-13  3

functools — Tools for Manipulating Functions

目的:functools 模块应用于高阶函数,即参数或(和)返回值为其他函数的函数。 通常来说,此模块的功能适用于所有可调用对象。 functools模块提供了用于改编或扩展函数以及其他可调用对象的工具,而无需完全重写它们。

Decorators

functools模块提供的主要工具是partial类,可用于使用默认参数“包装”可调用对象。结果对象本身是可调用的,并且可以将其视为原始函数。它采用与原始参数相同的所有参数,并且也可以使用额外的位置参数或命名参数来调用。可以使用partial而不是lambda为函数提供默认参数,同时保留一些未指定的参数。

Partial Objects

此示例显示了函数myfunc()的两个简单的部分对象。 show_details()的输出包括部分对象的func,args和keyword属性。

import functools def myfunc(a, b=2): "Docstring for myfunc()." print(' called myfunc with:', (a, b)) def show_details(name, f, is_partial=False): "Show details of a callable object." print('{}:'.format(name)) print(' object:', f) if not is_partial: print(' __name__:', f.__name__) if is_partial: print(' func:', f.func) print(' args:', f.args) print(' keywords:', f.keywords) return show_details('myfunc', myfunc) myfunc('a', 3) print() # Set a different default value for 'b', but require # the caller to provide 'a'. p1 = functools.partial(myfunc, b=4) show_details('partial with named default', p1, True) p1('passing a') p1('override b', b=5) print() # Set default values for both 'a' and 'b'. p2 = functools.partial(myfunc, 'default a', b=99) show_details('partial with defaults', p2, True) p2() p2(b='override b') print() print('Insufficient arguments:') p1()

在示例末尾,创建的第一个部分被调用而没有传递a的值,从而导致异常。

myfunc: object: <function myfunc at 0x1007a6a60> __name__: myfunc called myfunc with: ('a', 3) partial with named default: object: functools.partial(<function myfunc at 0x1007a6a60>, b=4) func: <function myfunc at 0x1007a6a60> args: () keywords: {'b': 4} called myfunc with: ('passing a', 4) called myfunc with: ('override b', 5) partial with defaults: object: functools.partial(<function myfunc at 0x1007a6a60>, 'default a', b=99) func: <function myfunc at 0x1007a6a60> args: ('default a',) keywords: {'b': 99} called myfunc with: ('default a', 99) called myfunc with: ('default a', 'override b') Insufficient arguments: Traceback (most recent call last): File "functools_partial.py", line 51, in <module> p1() TypeError: myfunc() missing 1 required positional argument: 'a'

简单来说,partial的功能就是把一个函数的某些参数固定,返回一个新的函数

Acquiring Function Properties

默认情况下,部分对象不具有__name__或__doc__属性,并且如果没有这些属性,则修饰后的函数更难以调试。使用update_wrapper(),将属性从原始函数复制或添加到部分对象。

import functools def 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列出要修改的值。

myfunc: 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 with partial 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().'

Other Callables

局部函数可用于任何可调用对象,而不仅适用于独立函数。

import functools class 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__)) return o = 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 __()方法从类的实例创建部分。

instance: 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 at 0x1011b1cf8>, 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)

Methods and Functions

尽管partial()返回一个可调用的对象准备好直接使用,但是partialmethod()返回一个可调用的方法用作对象的未绑定方法。在下面的示例中,两次将相同的独立函数添加为MyClass的属性,一次将partialmethod()用作method1(),再次将partial()用作method2()。

import functools def standalone(self, a=1, b=2): "Standalone function" print(' called standalone with:', (self, a, b)) if self is not None: print(' self.attr =', self.attr) class MyClass: "Demonstration class for 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))

可以从MyClass的实例中调用method1(),并且该实例作为第一个参数传递,就像通常定义的方法一样。 method2()未设置为绑定方法,因此必须显式传递self参数,否则调用将导致TypeError。

standalone called standalone with: (None, 1, 2) method1 as partialmethod called standalone with: (<__main__.MyClass object at 0x1007b1d30>, 1, 2) self.attr = instance attribute method2 as partial ERROR: standalone() missing 1 required positional argument: 'self'

Acquiring Function Properties for Decorators

当在装饰器中使用时,更新包装的可调用方法的属性特别有用,因为转换后的函数最终具有原始“裸”函数的属性。

import functools 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() 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 decorated def myfunc(a, b=2): "myfunc() is not complicated" print(' myfunc:', (a, b)) return # The raw function show_details('myfunc', myfunc) myfunc('unwrapped, default b') myfunc('unwrapped, passing b', 3) print() # Wrap explicitly wrapped_myfunc = simple_decorator(myfunc) show_details('wrapped_myfunc', wrapped_myfunc) wrapped_myfunc() wrapped_myfunc('args to wrapped', 4) print() # Wrap with decorator syntax @simple_decorator def decorated_myfunc(a, b): myfunc(a, b) return show_details('decorated_myfunc', decorated_myfunc) decorated_myfunc() decorated_myfunc('args to decorated', 4)

functools提供了一个装饰器wraps(),它将update_wrapper()应用于装饰后的函数。

myfunc: object: <function myfunc at 0x101241b70> __name__: myfunc __doc__ 'myfunc() is not complicated' myfunc: ('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__ None decorated: ('decorated defaults', 1) myfunc: ('decorated defaults', 1) decorated: ('args to decorated', 4) myfunc: ('args to decorated', 4)

Comparison

在Python 2下,类可以定义__cmp ()方法,该方法根据对象是小于,等于还是大于要比较的项目返回-1、0或1。 Python 2.1引入了丰富的比较方法API( lt (), le (), eq (), ne (), gt __()和__ge __()),它们执行单个比较操作并返回布尔值。为了支持这些新方法,Python 3不推荐使用__cmp __(),并且functools提供了一些工具,使编写符合Python 3中新比较要求的类变得更加容易。

Rich Comparison

丰富的比较API旨在允许具有复杂比较的类以最有效的方式实现每个测试。但是,对于比较相对简单的类,手动创建每种丰富的比较方法没有意义。 total_ordering()类修饰器采用一个提供一些方法的类,并添加其余方法。

import functools import inspect from pprint import pprint @functools.total_ordering class MyObject: def __init__(self, val): self.val = val def __eq__(self, other): print(' testing __eq__({}, {})'.format( self.val, other.val)) return self.val == other.val def __gt__(self, other): print(' testing __gt__({}, {})'.format( self.val, other.val)) return self.val > other.val print('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))

该类必须提供__eq __()的实现和另一种丰富的比较方法。装饰器通过使用提供的比较来添加其余工作方法的实现。如果无法进行比较,则该方法应返回NotImplemented,以便在完全失败之前,可以使用另一个对象上的反向比较运算符尝试进行比较。

Methods: [('__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: True a <= b: testing __gt__(1, 2) result of a <= b: True a == b: testing __eq__(1, 2) result of a == b: False a >= b: testing __gt__(1, 2) testing __eq__(1, 2) result of a >= b: False a > b : testing __gt__(1, 2) result of a > b: False

Collation Order

由于Python 3中不推荐使用老式比较函数,因此不再支持sort()之类的cmp参数。使用比较功能的较旧程序可以使用cmp_to_key()将其转换为返回归类键的函数,该键用于确定最终序列中的位置。

import functools class MyObject: def __init__(self, val): self.val = val def __str__(self): return 'MyObject({})'.format(self.val) def compare_obj(a, b): """Old-style comparison function. """ print('comparing {} and {}'.format(a, b)) if a.val < b.val: return -1 elif a.val > b.val: return 1 return 0 # Make a key function using cmp_to_key() get_key = functools.cmp_to_key(compare_obj) def get_key_wrapper(o): "Wrapper function for get_key to allow for print statements." new_key = get_key(o) print('key_wrapper({}) -> {!r}'.format(o, new_key)) return new_key objs = [MyObject(x) for x in range(5, 0, -1)] for o in sorted(objs, key=get_key_wrapper): print(o)

通常,将直接使用cmp_to_key(),但是在此示例中,引入了一个额外的包装器函数,以在调用键函数时打印出更多信息。 输出显示sorted()首先为序列中的每个项目调用get_key_wrapper()以产生一个密钥。 cmp_to_key()返回的键是functools中定义的类的实例,该类使用传入的旧式比较函数来实现丰富的比较API。创建所有键之后,通过比较键对序列进行排序。

key_wrapper(MyObject(5)) -> <functools.KeyWrapper object at 0x1011c5530> key_wrapper(MyObject(4)) -> <functools.KeyWrapper object at 0x1011c5510> key_wrapper(MyObject(3)) -> <functools.KeyWrapper object at 0x1011c54f0> key_wrapper(MyObject(2)) -> <functools.KeyWrapper object at 0x1011c5390> key_wrapper(MyObject(1)) -> <functools.KeyWrapper object at 0x1011c5710> 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)

Caching

lru_cache()装饰器将函数包装在最近最少使用的缓存中。该函数的参数用于构建哈希键,然后将其映射到结果。随后具有相同参数的调用将从缓存中获取值,而不是调用该函数。装饰器还向该函数添加方法以检查缓存的状态(cache_info())并清空缓存(cache_clear())。

import functools @functools.lru_cache() def expensive(a, b): print('expensive({}, {})'.format(a, b)) return a * b MAX = 2 print('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())

本示例在一组嵌套循环中多次调用costume()。第二次使用相同的值进行调用时,结果将显示在缓存中。清除缓存并再次运行循环后,必须重新计算值。

First 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参数为每个缓存更改。

import functools @functools.lru_cache(maxsize=2) def expensive(a, b): print('called expensive({}, {})'.format(a, b)) return a * b def make_call(a, b): print('({}, {})'.format(a, b), end=' ') pre_hits = expensive.cache_info().hits expensive(a, b) post_hits = expensive.cache_info().hits if 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)时,将删除缓存中最旧的项,并用新结果替换。

Establish the cache (1, 2) called expensive(1, 2) (2, 3) called expensive(2, 3) Use cached items (1, 2) cache hit (2, 3) cache hit Compute a new value, triggering cache expiration (3, 4) called expensive(3, 4) Cache still contains one old item (2, 3) cache hit Oldest item needs to be recomputed (1, 2) called expensive(1, 2)

由lru_cache()管理的缓存的键必须是可哈希的,因此包装有缓存查找的函数的所有参数都必须是可哈希的。

import functools @functools.lru_cache(maxsize=2) def expensive(a, b): print('called expensive({}, {})'.format(a, b)) return a * b def make_call(a, b): print('({}, {})'.format(a, b), end=' ') pre_hits = expensive.cache_info().hits expensive(a, b) post_hits = expensive.cache_info().hits if 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)) If any object that can’t be hashed is passed in to the function, a TypeError is raised. (1, 2) called expensive(1, 2) ([1], 2) ERROR: unhashable type: 'list' (1, {'2': 'two'}) ERROR: unhashable type: 'dict'

Reducing a Data Set

reduce()函数将一个可调用的数据序列和一个数据序列作为输入,并基于使用该序列中的值调用可调用对象并累加结果输出,从而生成单个值作为输出。

import functools def do_reduce(a, b): print('do_reduce({}, {})'.format(a, b)) return a + b data = range(1, 5) print(data) result = functools.reduce(do_reduce, data) print('result: {}'.format(result))

本示例将输入序列中的数字相加。

range(1, 5) do_reduce(1, 2) do_reduce(3, 3) do_reduce(6, 4) result: 10

可选的初始值设定项参数位于序列的最前面,并与其他项一起处理。这可用于使用新输入来更新先前计算的值。

import functools def do_reduce(a, b): print('do_reduce({}, {})'.format(a, b)) return a + b data = range(1, 5) print(data) result = functools.reduce(do_reduce, data, 99) print('result: {}'.format(result))

在此示例中,先前的总和99用于初始化由reduce()计算的值。

range(1, 5) do_reduce(99, 1) do_reduce(100, 2) do_reduce(102, 3) do_reduce(105, 4) result: 109

当没有初始化程序时,具有单个项目的序列会自动减少到该值。除非提供了初始化程序,否则空列表会产生错误。

import functools def do_reduce(a, b): print('do_reduce({}, {})'.format(a, b)) return a + b print('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而不是传递初始化程序。

Single item in sequence: 1 do_reduce(99, 1) Single item in sequence with initializer: 100 Empty sequence with initializer: 99 ERROR: reduce() of empty sequence with no initial value

Generic Functions

在像Python这样的动态类型语言中,通常需要根据参数的类型执行略有不同的操作,尤其是在处理项目列表和单个项目之间的差异时。直接检查参数的类型非常简单,但是在行为差异可以隔离到单独的函数中的情况下,functools提供了singledispatch()装饰器来注册一组通用函数,以便根据第一个函数的类型进行自动切换函数的参数。

import functools @functools.singledispatch def 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情况。

default myfunc('string argument') myfunc_int(1) default myfunc(2.3) myfunc_list() a b c

如果找不到该类型的完全匹配,则将评估继承顺序并使用最接近的匹配类型。

import functools class A: pass class B(A): pass class C(A): pass class D(B): pass class E(C, D): pass @functools.singledispatch def 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与任何已注册的通用函数都不完全匹配,并且所选函数取决于类层次结构。

myfunc_A(A) myfunc_B(B) myfunc_C(C) myfunc_B(D) myfunc_C(E)
最新回复(0)