Python标准库之functools

引言

functoolsitertoolsoperator是Python标准库为我们提供的支持函数式编程的三大模块,合理的使用这三个模块,我们可以写出更加简洁可读的Pythonic代码,接下来我们通过一些example来了解三大模块的使用。

functools的使用

functools是Python中很重要的模块,它提供了一些非常有用的高阶函数。高阶函数就是说一个可以接受函数作为参数或者以函数作为返回值的函数,因为Python中函数也是对象,因此很容易支持这样的函数式特性

partial

函数声明如下:

functools.partial(func,*args, **keywords)

返回一个可以像函数一样被调用的partial实例,在调用时使用args和keywords参数。使用python实现时,类似于:

def partial(func, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = keywords.copy()
        newkeywords.update(fkeywords)
        return func(*(args + fargs), **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

通常的使用方法

通常的用法是在原函数声明的参数中,从前往后连续将参数值固定:

from functools import partial
def test_partial(a, b, c, d):
    print a,b,c,d
test1 = partial(test_partial,1,2)
test1(3,4)
# 1 2 3 4
test2 = partial(test_partial,1,2,3,4)
test2(3)
# Traceback (most recent call last):
#  File "", line 1, in 
# TypeError: test_partial() takes exactly 4 arguments (5 given)

这通常只能把前面的参数固定,假如有个需求和现有的不一样,需要使后面的参数固定,该怎么做?可以使用新方法,详见原文:https://blog.csdn.net/qq_39469761/article/details/74939573

partialmethod

partialmethod和partial类似,但是对于绑定一个非对象自身的方法的时候,这个时候就只能使用partialmethod了,我们通过下面这个例子来看一下两者的差异。

 

from functools import partial, partialmethod
def standalone(self, a=1, b=2):
    "Standalone function"
    print('  called standalone with:', (self, a, b))
    if self is not None:
        print('  self.attr =', self.attr)
class MyClass:
    "Demonstration class for functools"
    def __init__(self):
        self.attr = 'instance attribute'
    method1 = functools.partialmethod(standalone)  # 使用partialmethod
    method2 = functools.partial(standalone)  # 使用partial
>>> o = MyClass()
>>> o.method1()
  called standalone with: (<__main__.MyClass object at 0x7f46d40cc550>, 1, 2)
  self.attr = instance attribute
# 不能使用partial
>>> o.method2()
Traceback (most recent call last):
  File "", line 1, in 
TypeError: standalone() missing 1 required positional argument: 'self' 

singledispatch

虽然Python不支持同名方法允许有不同的参数类型,但是我们可以借用singledispatch来动态指定相应的方法所接收的参数类型,而不用把参数判断放到方法内部去判断从而降低代码的可读性。

from functools import singledispatch
class TestClass(object):
    @singledispatch
    def test_method(arg, verbose=False):
        if verbose:
            print("Let me just say,", end=" ")
        print(arg)
    @test_method.register(int)
    def _(arg):
        print("Strength in numbers, eh?", end=" ")
        print(arg)
    @test_method.register(list)
    def _(arg):
        print("Enumerate this:")
        for i, elem in enumerate(arg):
            print(i, elem)

下面通过@test_method.register(int)和@test_method.register(list)指定当test_method的第一个参数为int或者list的时候,分别调用不同的方法来进行处理

>>> TestClass.test_method(55555)  # call @test_method.register(int)
Strength in numbers, eh? 55555
>>> TestClass.test_method([33, 22, 11])   # call @test_method.register(list)
Enumerate this:
0 33
1 22
2 11
>>> TestClass.test_method('hello world', verbose=True)  # call default
Let me just say, hello world

wraps

装饰器会遗失被装饰函数的__name__和__doc__等属性,可以使用@wraps来恢复。

from functools import wraps
def my_decorator(f):
    @wraps(f)
    def wrapper():
        """wrapper_doc"""
        print('Calling decorated function')
        return f()
    return wrapper
@my_decorator
def example():
    """example_doc"""
    print('Called example function')
>>> example.__name__
'example'
>>> example.__doc__
'example_doc'
# 尝试去掉@wraps(f)来看一下运行结果,example自身的__name__和__doc__都已经丧失了
>>> example.__name__
'wrapper'
>>> example.__doc__
'wrapper_doc'

 我们也可以使用update_wrapper来改写。

from itertools import update_wrapper
def g():
    ...
g = update_wrapper(g, f)
# 等价于
@wraps(f)
def g():
    ...

@wraps内部实际上就是基于update_wrapper来实现的。

def wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES):
    def decorator(wrapper):
        return update_wrapper(wrapper, wrapped=wrapped...)
    return decorator

total_ordering

Python2中可以通过自定义__cmp__的返回值0/-1/1来比较对象的大小,在Python3中废弃了__cmp__,但是我们可以通过totalordering然后修改 _lt__() , __le__() , __gt__(), __ge__(), __eq__(), __ne__() 等魔术方法来自定义类的比较规则。p.s: 如果使用必须在类里面定义 __lt__() , __le__() , __gt__(), __ge__()中的一个,以及给类添加一个__eq__() 方法。

import functools
@functools.total_ordering
class MyObject:
    def __init__(self, val):
        self.val = val
    def __eq__(self, other):
        print('  testing __eq__({}, {})'.format(
            self.val, other.val))
        return self.val == other.val
    def __gt__(self, other):
        print('  testing __gt__({}, {})'.format(
            self.val, other.val))
        return self.val > other.val
a = MyObject(1)
b = MyObject(2)
for expr in ['a < b', 'a <= b', 'a == b', 'a >= b', 'a > b']:
    print('\n{:<6}:'.format(expr))
    result = eval(expr)
    print('  result of {}: {}'.format(expr, result))

下面是运行结果:

a < b :
  testing __gt__(1, 2)
  testing __eq__(1, 2)
  result of a < b: True
a <= b:
  testing __gt__(1, 2)
  result of a <= b: True
a == b:
  testing __eq__(1, 2)
  result of a == b: False
a >= b:
  testing __gt__(1, 2)
  testing __eq__(1, 2)
  result of a >= b: False
a > b :
  testing __gt__(1, 2)
  result of a > b: False

 lru_cache

lru_cache和singledispatch是开发中应用非常广泛的黑魔法,接下来我们来看一下lru_cache。对于重复的计算性任务,使用缓存加速是非常重要的,下面我们通过一个fibonacci的例子来看一下使用lru_cache与不使用lru_cache在速度上的差异。

# clockdeco.py
 
import time
import functools
 
 
def clock(func):
    @functools.wraps(func)
    def clocked(*args, **kwargs):
        t0 = time.time()
        result = func(*args, **kwargs)
        elapsed = time.time() - t0
        name = func.__name__
        arg_lst = []
        if args:
            arg_lst.append(', '.join(repr(arg) for arg in args))
        if kwargs:
            pairs = ['%s=%r' % (k, w) for k, w in sorted(kwargs.items())]
            arg_lst.append(', '.join(pairs))
        arg_str = ', '.join(arg_lst)
        print('[%0.8fs] %s(%s) -> %r ' % (elapsed, name, arg_str, result))
        return result
    return clocked

不使用lru_cache

from clockdeco import clock
 
 
@clock
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-2) + fibonacci(n-1)
 
 
if __name__=='__main__':
    print(fibonacci(6))

 下面是运行结果,从运行结果可以看出fibonacci(n)会在递归的时候被重复计算,这是非常耗时消费资源的。

[0.00000119s] fibonacci(0) -> 0 
[0.00000143s] fibonacci(1) -> 1 
[0.00021172s] fibonacci(2) -> 1 
[0.00000072s] fibonacci(1) -> 1 
[0.00000095s] fibonacci(0) -> 0 
[0.00000095s] fibonacci(1) -> 1 
[0.00011444s] fibonacci(2) -> 1 
[0.00022793s] fibonacci(3) -> 2 
[0.00055265s] fibonacci(4) -> 3 
[0.00000072s] fibonacci(1) -> 1 
[0.00000072s] fibonacci(0) -> 0 
[0.00000095s] fibonacci(1) -> 1 
[0.00011158s] fibonacci(2) -> 1 
[0.00022268s] fibonacci(3) -> 2 
[0.00000095s] fibonacci(0) -> 0 
[0.00000095s] fibonacci(1) -> 1 
[0.00011349s] fibonacci(2) -> 1 
[0.00000072s] fibonacci(1) -> 1 
[0.00000095s] fibonacci(0) -> 0 
[0.00000095s] fibonacci(1) -> 1 
[0.00010705s] fibonacci(2) -> 1 
[0.00021267s] fibonacci(3) -> 2 
[0.00043225s] fibonacci(4) -> 3 
[0.00076509s] fibonacci(5) -> 5 
[0.00142813s] fibonacci(6) -> 8 
8

使用lru_cache

import functools
from clockdeco import clock
 
 
@functools.lru_cache() # 1
@clock # 2
def fibonacci(n):
    if n < 2:
       return n
    return fibonacci(n-2) + fibonacci(n-1)
 
if __name__=='__main__':
    print(fibonacci(6))

下面是运行结果,对于已经计算出来的结果将其放入缓存。

[0.00000095s] fibonacci(0) -> 0 
[0.00005770s] fibonacci(1) -> 1 
[0.00015855s] fibonacci(2) -> 1 
[0.00000286s] fibonacci(3) -> 2 
[0.00021124s] fibonacci(4) -> 3 
[0.00000191s] fibonacci(5) -> 5 
[0.00024652s] fibonacci(6) -> 8 
8

 上面我们选用的数字还不够大,感兴趣的朋友不妨自己选择一个较大的数字比较一下两者在速度上的差异

reduce() 

详情入口:https://blog.csdn.net/q389797999/article/details/81702966

函数会对参数序列中元素进行累积。

函数将一个数据集合(链表,元组等)中的所有数据进行下列操作:用传给 reduce 中的函数 function(有两个参数)先对集合中的第 1、2 个元素进行操作,得到的结果再与第三个数据用 function 函数运算,最后得到一个结果

语法

reduce() 函数语法:

reduce(function, iterable, initializer)

参数

  • function -- 有两个参数的函数, 必需参数
  • iterable -- tuple ,list ,dictionary, string等可迭代物,必需参数
  • initializer -- 初始值, 可选参数

如果提供initial参数,会以sequence中的第一个元素和initial作为参数调用function,否则会以序列sequence中的前两个元素做参数调用function。

返回值

返回函数计算结果。

 

 

你可能感兴趣的:(Python标准库之functools)