今天跟同事联调聊到functools,详细整理了一下functools的功能:
1.概述
functools,用于高阶函数:指那些作用于函数或者返回其它函数的函数,通常只要是可以被当做函数调用的对象就是这个模块的目标。
- cmp_to_key,将一个比较函数转换关键字函数;
- lru_cache(maxsize=128, typed=False),提供相同参数调用函数时的缓存,再次调用直接返回结果
- partial,针对函数起作用,并且是部分的;
- reduce,与python内置的reduce函数功能一样;
- total_ordering,在类装饰器中按照缺失顺序,填充方法;
- update_wrapper,更新一个包裹(wrapper)函数,使其看起来更像被包裹(wrapped)的函数;
- wraps,可用作一个装饰器,简化调用update_wrapper的过程
2.详细解释
2.1 cmp_to_key(func)
为版本3.2中新增。
作用:
将旧风格的比较函数转换为key函数。用于接收key函数的工具(例如sorted(),min(),max(),heapq.nlargest(),heapq.nsmallest(),itertools.groupby())。该函数主要用作支持比较函数的Python2转换工具。
比较函数可以是任何可调用的对象,接收两个参数,比较它们,如果小于返回负数,相等返回0,大于返回正数。key函数是一个可调用对象,接收一个参数,并返回另一个值用于排序的键。
示例:
from functools import cmp_to_key
def compare(ele1,ele2):
return ele2 - ele1
a = [2,3,1]
print sorted(a, key = cmp_to_key(compare))
输出:
[3, 2, 1]
2.2 lru_cache(maxsize=128, typed=False)
一个提供缓存功能的装饰器,包装一个函数,缓存其maxsize组传入参数,在下次以相同参数调用时直接返回上一次的结果。用以节约高开销或I/O函数的调用时间。
由于使用了字典存储缓存,所以该函数的固定参数和关键字参数必须是可哈希的。 不同模式的参数可能被视为不同从而产生多个缓存项,例如,
f(a=1, b=2) 和 f(b=2, a=1) 因其参数顺序不同,可能会被缓存两次。 如果 maxsize 设置为 None
,LRU功能将被禁用且缓存数量无上限。 maxsize 设置为2的幂时可获得最佳性能。 如果 typed
设置为true,不同类型的函数参数将被分别缓存。例如, f(3) 和 f(3.0) 将被视为不同而分别缓存。
为了帮助测量缓存的有效性并调整maxsize参数,包装函数使用cache_info()函数进行检测,该函数返回一个命名元组,显示hits,
misses,maxsize和currsize。在多线程环境中,hits和misses是近似值。
该装饰器也提供了一个用于清理/使缓存失效的函数 cache_clear() 。
原始的底层函数通过wrapped属性访问。这对于debug查错,绕过缓存,或者重新装饰函数很有用。
当最近调用是即将调用的最佳调用因子时(例如,新闻服务器上的最受欢迎文章常常每天改变),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)
2.3 total_ordering
这个装饰器是在python2.7的时候加上的,它是针对某个类如果定义了__lt__、le、gt、__ge__这些方法中的至少一个,使用该装饰器,则会自动的把其他几个比较函数也实现在该类中。
示例:
@total_ordering
class Student:
def __eq__(self, other):
return ((self.lastname.lower(), self.firstname.lower()) ==
(other.lastname.lower(), other.firstname.lower()))
def __lt__(self, other):
return ((self.lastname.lower(), self.firstname.lower()) <
(other.lastname.lower(), other.firstname.lower()))
print dir(Student)
得到:
['__doc__', '__eq__', '__ge__', '__gt__', '__le__', '__lt__', '__module__']
2.4 partial(func, *args, **keywords)
一个函数可以有多个参数,而在有的情况下有的参数先得到,有的参数需要在后面的情景中才能知道,python 给我们提供了partial函数用于携带部分参数生成一个新函数。。 大致等价于:
#args/keywords 调用partial时参数 def partial(func, *args, **keywords): def newfunc(*fargs, **fkeywords): newkeywords = keywords.copy() newkeywords.update(fkeywords) return func(*(args + fargs), **newkeywords) #合并,调用原始函数,此时用了partial的参数 newfunc.func = func newfunc.args = args newfunc.keywords = keywords return newfunc ```
示例:
import functools
def add(a, b):
return a + b
add(4, 2)
6
plus3 = functools.partial(add, 3)
plus5 = functools.partial(add, 5)
plus3(4)
7
plus3(7)
10
plus5(10)
15
另外需要注意的是,partial()不会保留封装函数的元数据,比如注释文档、注解等。
应用:
适用于一些部分参数已知,想要减少参数传递的情况。
2.5 partialmethod(func, *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
2.6 reduce(function, iterable, [initializer])
将两个参数的function从左至右依次作用于序列中的项,减少序列为单个值。例如,reduce(lambda x, y: x+y, [1,
2, 3, 4,
5])会计算((((1+2)+3)+4)+5)。左边的参数x是计算出的值,右边的参数y是从序列中更新的值。如果提供了可选参数initializer,在计算中,它会放在序列项之前,当序列为空时,提供一个默认值。如果没指定initializer,并且序列只有包含一项,会返回第一项。大致等价于:it = iter(iterable) if initializer is None: value = next(it) else: value = initializer for element in it: value = function(value, element) return value ```
2.7 update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)
默认partial对象没有__name__和__doc__, 这种情况下,对于装饰器函数非常难以debug.使用update_wrapper(),从原始对象拷贝或加入现有partial对象
它可以把被封装函数的__name__、module、__doc__和 __dict__都复制到封装函数去(模块级别常量WRAPPER_ASSIGNMENTS, WRAPPER_UPDATES)
>>> functools.WRAPPER_ASSIGNMENTS
('__module__', '__name__', '__doc__')
>>> functools.WRAPPER_UPDATES
('__dict__',)
这个函数主要用在装饰器函数中,装饰器返回函数反射得到的是包装函数的函数定义而不是原始函数定义
#!/usr/bin/env python
# encoding: utf-8
def wrap(func):
def call_it(*args, **kwargs):
"""wrap func: call_it"""
print 'before call'
return func(*args, **kwargs)
return call_it
@wrap
def hello():
"""say hello"""
print 'hello world'
from functools import update_wrapper
def wrap2(func):
def call_it(*args, **kwargs):
"""wrap func: call_it2"""
print 'before call'
return func(*args, **kwargs)
return update_wrapper(call_it, func)
@wrap2
def hello2():
"""test hello"""
print 'hello world2'
if __name__ == '__main__':
hello()
print hello.__name__
print hello.__doc__
print
hello2()
print hello2.__name__
print hello2.__doc__
得到的输出:
before call
hello world
call_it
wrap func: call_it
before call
hello world2
hello2
test hello
2.8 wraps
调用函数装饰器partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)的简写,相当于update_wrapper的装饰器版本。
示例
from functools import wraps
def wrap3(func):
@wraps(func)
def call_it(*args, **kwargs):
"""wrap func: call_it2"""
print 'before call'
return func(*args, **kwargs)
return call_it
@wrap3
def hello3():
"""test hello 3"""
print 'hello world3'
结果:
before call
hello world3
hello3
test hello 3