简单的介绍一下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

就以上这些了。