简单的介绍一下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
就以上这些了。
参考
1. https://blog.csdn.net/weixin_40156487/article/details/82460854
2. https://www.cnblogs.com/zhbzz2007/p/6001827.html
3. 官方文档 https://docs.python.org/3/library/functools.html?highlight=functool#module-functools
4. https://zhuanlan.zhihu.com/p/27643991