Python自带的 functools 模块提供了一些常用的高阶函数,也就是用于处理其它函数的特殊函数。换言之,就是能使用该模块对可调用对象进行处理。英文文档
一、functools模块函数概览
- functools.cmp_to_key(func)
- functools.total_ordering(cls)
- functools.reduce(function, iterable[, initializer])
- functools.partial(func[, args][, *keywords])
- functools.update_wrapper(wrapper, wrapped[, assigned][, updated])
- functools.wraps(wrapped[, assigned][, updated])
- functools.lru_cache(maxsize=128, typed=False)
- functools.partialmethod(func, *args, **keywords)
- functools.singledispatch(default)
二、functools.cmp_to_key()
语法:
functools.cmp_to_key(func)
该函数用于将旧式的比较函数转换为关键字函数。
旧式的比较函数:接收两个参数,返回比较的结果。返回值小于零则前者小于后者,返回值大于零则相反,返回值等于零则两者相等。
关键字函数:接收一个参数,返回其对应的可比较对象。例如 sorted(), min(), max(), heapq.nlargest(), heapq.nsmallest(), itertools.groupby() 都可作为关键字函数。
在 Python 3 中,有很多地方都不再支持旧式的比较函数,此时可以使用 cmp_to_key() 进行转换。
示例:
1 |
|
三、functools.total_ordering()
版本3.2中新增。
版本3.4中修改:如果是不能识别的类型,现在支持从底层比较函数返回NotImplemented。
语法:
functools.total_ordering(cls)
这是一个类装饰器,用于自动实现类的比较运算。
只需要在类中实现 __eq__() 方法和以下方法中的任意一个 __lt__(), __le__(), __gt__(), __ge__(),那么 total_ordering() 就能自动帮我们实现余下的几种比较运算。
示例:
1 2 3 4 5 6 7 8 |
|
给定的一个类定义了一个或多个富比较方法,该类装饰器提供剩下的。这简化了指定所有富比较操作的工作量。
注意:虽然该装饰器能很容易的创建行为良好的完全有序类型,但会导致衍生出的比较函数执行的更慢,以及更复杂的堆栈跟踪。如果性能基准测试表明这是程序的瓶颈,则实现所有六个富比较函数可能会是提高速度的方式。
四、functools.reduce()
语法:
functools.reduce(function, iterable[, initializer])
该函数与 Python 内置的 reduce() 函数相同,主要用于编写兼容 Python 3 的代码。
五、functools.partial()
语法:
functools.partial(func[, *args][, **keywords])
该函数返回一个 partial 对象,调用该对象的效果相当于调用 func 函数,并传入位置参数 args 和关键字参数 keywords 。如果调用该对象时传入了位置参数,则这些参数会被添加到 args 中。如果传入了关键字参数,则会被添加到 keywords 中。
partial() 函数的等价实现大致如下:
1 2 3 4 5 6 7 8 9 |
|
partial() 函数主要用于“冻结”某个函数的部分参数,返回一个参数更少、使用更简单的函数对象。
示例:
1 2 3 4 5 |
|
partial()
创建可调用的 partial 对象。它们有三个只读属性:
5.1 partial.func
一个可调用的对象或函数。调用partial对象会转为使用新的参数和关键字参数调用func。
5.2 partial.args
最左边的位置参数会优先作为位置参数提供给partial对象调用。
5.3 partial.keywords
partial 对象被调用时提供关键字参数。
partial 对象与函数对象类似,它们可以被调用,有弱引用,并且可以有属性。但有一些重要的区别。对于实例,__name__
和__doc__
属性不会自动创建。同时,在类中定义的partial对象的行为类似静态方法,在实例属性查找时,不会转换为绑定方法。
六、functools.update_wrapper()
语法:
functools.update_wrapper(wrapper, wrapped[, assigned][, updated])
该函数用于更新包装函数(wrapper),使它看起来像原函数一样。可选的参数是一个元组,assigned 元组指定要直接使用原函数的值进行替换的属性,updated 元组指定要对照原函数进行更新的属性。这两个参数的默认值分别是模块级别的常量:WRAPPER_ASSIGNMENTS 和 WRAPPER_UPDATES。前者指定了对包装函数的 __name__, __module__, __doc__ 属性进行直接赋值,而后者指定了对包装函数的 __dict__ 属性进行更新。
该函数主要用于装饰器函数的定义中,置于包装函数之前。如果没有对包装函数进行更新,那么被装饰后的函数所具有的元信息就会变为包装函数的元信息,而不是原函数的元信息。
七、functools.wraps()
语法:
functools.wraps(wrapped[, assigned][, updated])
wraps() 简化了 update_wrapper() 函数的调用。它等价于 partial(update_wrapper, wrapped=wrapped, assigned, updated=updated)。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
如果不使用这个函数,示例中的函数名就会变成 wrapper ,并且原函数 example() 的说明文档(docstring)就会丢失。
八、@functools.lru_cache(maxsize=128, typed=False)
版本3.2中新增。
版本3.3中修改:增加可选参数typed参数。
装饰器用一个有记忆的调用包装一个函数,它可以保存最近maxsize次调用。当使用同样的参数定期调用费时或I/O绑定的函数时,它可以节省时间。
因为使用字典缓存结果,所以函数的位置和关键字参数必须是hashable。
如果maxsize设置为None,则禁用LRU功能,并且缓存可以无限增长。当maxsize设置为$ 2^n $时,性能最佳。
如果typed设置为真,则不同类型的函数参数会分别缓存。例如,f(3)
和f(3.0)
将视为不同结果的不同调用。
为了帮助测量缓存的有效性并调整maxsize参数,包装函数使用cache_info()
函数返回一个命名元组,包括hits,misses,maxsize和currsize。在多线程环境中,hits和misses是近似值。
装饰器还提供了cache_clear()
函数用于清除缓存,或者让缓存失效。
原始的底层函数通过wrapped属性访问。这对于内省,绕过缓存,或者重新装饰函数很有用。
当最近调用是即将调用的最佳调用因子时(例如,新闻服务器上的最受欢迎文章常常每天改变),LRU(least recently used)缓存效果最好。缓存的大小限制确保缓存不会在长时间运行的进程(如web服务器)上不受限制的增长。
用于静态Web内容的LRU缓存示例:
@lru_cache(maxsize=32)
def get_pep(num):
'Retrieve text of a Python Enhancement Proposal'
resource = 'http://www.python.org/dev/peps/pep-%04d/' % num
try:
with urllib.request.urlopen(resource) as s:
return s.read()
except urllib.error.HTTPError:
return 'Not Found'
>>> for n in 8, 290, 308, 320, 8, 218, 320, 279, 289, 320, 9991:
... pep = get_pep(n)
... print(n, len(pep))
>>> get_pep.cache_info()
CacheInfo(hits=3, misses=8, maxsize=32, currsize=8)
使用缓存实现动态编程技术高效计算斐波那契数列的示例:
@lru_cache(maxsize=None)
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)
>>> [fib(n) for n in range(16)]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]
>>> fib.cache_info()
CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)
九、类方法 functools.partialmethod(func, *args, **keywords)
版本3.4中新增。
返回一个行为类似partial的新partialmethod描述符,除了它是用于方法定义,而不是直接调用。
func必须是一个descriptor或者可调用对象(两个对象都像常规函数一样作为descriptor)。
当func是一个descriptor(比如普遍的Python函数,classmethod()
,staticmethod()
,abstractmethod()
,或者其它partialmethod实例时,get的调用会委托给底层的descriptor,并返回一个适当的partial对象。
当func不是可调用的descriptor时,会动态创建一个适当的绑定方法。用于方法时,该行为类似普通的Python函数:self参数会插入为第一个位置参数,甚至在传递给partialmethod构造器的args和keywords之前。
示例:
>>> class Cell(object):
... 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
十、@functools.singledispatch(default)
版本3.4中新增。
将函数转换为single-dispatch generic函数。
使用@singledispatch装饰器定义generic函数。注意,dispatch发生在第一个参数的类型上,相应的创建函数:
>>> from functools import singledispatch
>>> @singledispatch
... def fun(arg, verbose=False):
... if verbose:
... print('Let me juset say,', end=' ')
... print(arg)
使用generic函数的register()
属性添加函数的重载实现。这是一个装饰器,接受一个类型参数,并装饰实现该类型操作的函数:
>>> @fun.register(int)
... def _(arg, verbose=False):
... if verbose:
... print('Strength in numbers, eh?', end=' ')
... print(arg)
...
>>> @fun.register(list)
... def _(arg, verbose=False):
... if verbose:
... print('Enumerate this:')
... for i, elem in enumerate(arg):
... print(i, elem)
为了能够注册lambda表达式和预先存在的函数,register()
可以用于函数形式:
>>> def nothing(arg, verbose=False):
... print('Nothing.')
...
>>> fun.register(type(None), nothind)
register()
属性返回未装饰的函数,可以使用装饰堆叠,pickling,以及为每个变体单独创建单元测试:
>>> @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
False
调用时,generic函数根据第一个参数的类型dispatch:
>>> 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
当没有注册特定类型的实现时,其方法解析顺序用于查找更通用的实现。用@singledispatch
装饰的原始函数是为object类型注册的,如果没有找到更好的实现,则使用它。
使用dispatch()
属性查看generic函数为指定类型选择哪个实现:
>>> fun.dispatch(float)
<function fun_num at 0x1035a2840>
>>> fun.dispatch(dict)
<function fun at 0x103fe0000>
使用只读属性registry
访问所有注册的实现:
>>> fun.registry.keys()
dict_keys([<class 'NoneType'>, <class 'int'>, <class 'object'>,
<class 'decimal.Decimal'>, <class 'list'>,
<class 'float'>])
>>> fun.registry[float]
<function fun_num at 0x1035a2840>
>>> fun.registry[object]
<function fun at 0x103fe0000>