简单的介绍一下functools标准模块方便自己查阅使用。
functools主要具有以下函数
cmp_to_key,将旧式的比较函数转换关键字函数;
@lru_cache, 装饰器,是一种优化技术,将耗时的操作结果缓存,避免重复操作
partial,偏函数,针对函数起作用,将函数的某几个参数固定,重新返回一个可调用对象
reduce,计算可迭代对象的累加值;
@total_ordering,类装饰器,为一个类添加比较的方法。
update_wrapper,更新一个包裹(wrapper)函数,使其看起来更像被包裹(wrapped)的函数,简而言之就是防止被装饰的函数的基本属性被修改。
@wraps,装饰器,简化调用update_wrapper的过程;
@singledispatch, 单分派泛函数,类似于c++的重载
具体使用方法:
functools.cmp_to_key(func)
旧式的比较函数是接受两个参数,返回负值,0和正值比较大小的,该函数将旧式函数转化为可以被sorted(), min(), max(), heapq.nlargest(), heapq.nsmallest()等函数接受的key。
demo:
import functools
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
def compare(p1, p2):
"""根据y坐标比较大小"""
if p1.y > p2.y:
return 1
elif p1.y == p2.y:
return 0
else:
return -1
if __name__ == '__main__':
lst = [Point(-x, 2 * x - 1) for x in range(5)]
res = sorted(lst, key=functools.cmp_to_key(compare))
print(res)
控制台输出按照y坐标排序的结果
[Point(x=0, y=-1), Point(x=-1, y=1), Point(x=-2, y=3), Point(x=-3, y=5), Point(x=-4, y=7)]
@functools.lru_cache(maxsize=128, typed=False)
这个装饰器实现了备忘的功能,是一项优化技术,把耗时的函数的结果保存起来,避免传入相同的参数时重复计算。lru 是(least recently used)的缩写,即最近最少使用原则。表明缓存不会无限制增长,一段时间不用的缓存条目会被扔掉。
这个装饰器支持传入参数,maxsize 是保存最近多少个调用的结果,最好设置为 2 的倍数,默认为 128。如果设置为 None 的话就相当于是 maxsize 为正无穷了。还有一个参数是 type,如果 type 设置为 true,即把不同参数类型得到的结果分开保存,如 f(3) 和 f(3.0) 会被区分开。
为了帮助调优maxsize参数,封装的函数使用cache_info()函数进行检测,该函数返回一个命名元组,显示hits, misses、maxsize和currsize。在多线程环境中,hits和misses是近似的。装饰器还提供了一个cache_clear()函数,用于清除或使缓存失效。
该装饰器一般用于需要反复利用以前计算出来的值的函数,如递归就是一个很好的例子。下面用官网所给出的斐波那契数列例子,并用CProfile模块检测效率。
import functools
import cProfile
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)
@functools.lru_cache()
def lru_fib(n):
if n < 2:
return n
return lru_fib(n-1) + lru_fib(n-2)
if __name__ == '__main__':
cProfile.run('print(fib(35))', sort='cumtime')
cProfile.run('print(lru_fib(35))', sort='cumtime')
print(lru_fib.cache_info())
运行以上代码,控制台输出
9227465
29860707 function calls (5 primitive calls) in 8.239 seconds
Ordered by: cumulative time
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 8.239 8.239 {built-in method builtins.exec}
1 0.000 0.000 8.239 8.239 <string>:1(<module>)
29860703/1 8.239 0.000 8.239 8.239 lru_cache.py:14(fib)
1 0.000 0.000 0.000 0.000 {built-in method builtins.print}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
9227465
40 function calls (5 primitive calls) in 0.000 seconds
Ordered by: cumulative time
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.000 0.000 {built-in method builtins.exec}
1 0.000 0.000 0.000 0.000 <string>:1(<module>)
36/1 0.000 0.000 0.000 0.000 lru_cache.py:20(lru_fib)
1 0.000 0.000 0.000 0.000 {built-in method builtins.print}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
CacheInfo(hits=33, misses=36, maxsize=128, currsize=36)
结果显示未调用缓存优化的递归了2000多万次,而后者只调用了36次,用的时间也是差了很多,从这就可以看出缓存技术的优势。
functools.partial(func, *args, **keywords)
偏函数:partial函数的用法就是:将所作用的函数作为partial()函数的第一个参数,原函数的各个参数依次作为partial()函数的后续参数,原函数有关键字参数的一定要带上关键字,没有的话,按原有参数顺序进行补充。具体作用就是当函数的参数个数太多,需要简化时,使用 functools.partial 可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。当然,装饰器也可以实现,如果,我们不嫌麻烦的话。下面是例子。
import functools
def parabola(a, b, c, x):
"""求抛物线的值"""
return a*x*x + b*x + c
if __name__ == '__main__':
print(parabola(1, 1, 1, 5)) # 输出31
par = functools.partial(parabola, 1, 1, 1)
print(par(5)) # 输出31
print(par(1)) # 输出3
综上,所谓偏函数就是冻结原函数的部分参数,让下次调用可以减少填入的参数,(就是一种偷懒的方法)。
functools.reduce(function, iterable[, initializer])
将拥有两个参数的函数累积作用于可迭代对象的每一个元素上。例如reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) 依次计算((((1+2)+3)+4)+5),累加的和作为下一次迭代的第一个参数。
In[2]: import functools
In[3]: functools.reduce(lambda x, y: x+y, range(5))
Out[3]: 10
In[4]: functools.reduce(lambda x, y: x+y, range(5), 10)
Out[4]: 20
In[6]: functools.reduce(lambda x, y: x ^ y, range(5)) # 0^1^2^3^4
Out[6]: 4
@functools.total_ordering
为一个类补充剩余的比较方法,不用自己把所有的比较方法都写全。但是类本身必须已经实现__eq__方法,并且还实现了 __lt__(), __le__(), __gt__(), or __ge__()的其中一个方法。举个例子如下所示,如果不加该装饰器,直接比较s1>=s2会报错,加了装饰器后,就已经补全了比较的方法,可以让自己少写一些比较方法。
import functools
@functools.total_ordering
class Student(object):
def __init__(self, score):
self.score = score
def __eq__(self, other):
return self.score == other.score
def __gt__(self, other):
return self.score > other.score
if __name__ == '__main__':
s1 = Student(60)
s2 = Student(70)
print(s1 >= s2) # 输出False
functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)
@functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)
上面两个方法一起说,下面这个装饰器是上面这个方法的封装,利用这个装饰器可以保证被装饰函数的基本性质不变。基本性质有以下这些
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__', '__annotations__')
WRAPPER_UPDATES = ('__dict__',)
import functools
def _wrapper(func):
def inner(*args, **kwargs):
print('---inner---')
return func(*args, **kwargs)
return inner
def _wrapper2(func):
@functools.wraps(func)
def inner(*args, **kwargs):
print('---inner---')
return func(*args, **kwargs)
return inner
@_wrapper
def func():
"""f2"""
print('---func---')
@_wrapper2
def func2():
"""f2"""
print('---func2---')
if __name__ == '__main__':
print(func.__name__) # 输出inner
print(func.__doc__) # 输出None
print(func2.__name__) # 输出func2
print(func2.__doc__) # 输出f2
@functools.singledispatch
利用这个装饰器可以事先c++里的重载功能,python本身是动态语言,就不支持重载功能,如果要判断参数的类型,就可以用if...else判断,但利用这个单分派泛函数,就可以把普通的函数变为泛函数,达到c++等语言里的重载一样的效果。
import functools
@functools.singledispatch
def fun(arg):
print(type(arg), arg)
@fun.register(int)
def _(arg):
print('整数', type(arg), arg)
@fun.register(float)
def _(arg):
print('浮点数', type(arg), arg)
@fun.register(str)
def _(arg):
print('字符串', type(arg), arg)
if __name__ == '__main__':
fun(None) # 输出 <class 'NoneType'> None
fun(10) # 输出 整数 <class 'int'> 10
fun(1.2) # 输出 浮点数 <class 'float'> 1.2
fun('string') # 输出 字符串 <class 'str'> string
就以上这些了。