(转)Pythonfunctools详解

原文:https://zhuanlan.zhihu.com/p/131634488

一、简介

functools,用于高阶函数:指那些作用于函数或者返回其它函数的函数,通常只要是可以被当做函数调用的对象就是这个模块的目标。

在Python 2.7 中具备如下方法,

cmp_to_key,将一个比较函数转换关键字函数;(Python 3 不支持)

partial,针对函数起作用,并且是部分的;

reduce,与python内置的reduce函数功能一样;

total_ordering,在类装饰器中按照缺失顺序,填充方法;

update_wrapper,更新一个包裹(wrapper)函数,使其看起来更像被包裹(wrapped)的函数;

wraps,可用作一个装饰器,简化调用update_wrapper的过程;

二、方法

1、cmp_to_key

functools.cmp_to_key(func)

将老式鼻尖函数转换成key函数,用在接受key函数的方法中(such as sorted(), min(), max(), heapq.nlargest(), heapq.nsmallest(), itertools.groupby())

一个比较函数,接收两个参数,小于,返回负数,等于,返回0,大于返回整数

key函数,接收一个参数,返回一个表明该参数在期望序列中的位置

2、partial

functools.partial用于部分应用一个函数,它基于一个函数创建一个可调用对象,把原函数的某些参数固定,调用时只需要传递未固定的参数即可。
import functools

def add(a, b):
    print("当前结果值", a+b)

add = functools.partial(add, 1)
add(2)

# 输出
当前结果值3

add函数原本接收两个参数a和b,经过partial包装之后,a参数的值被固定为了1,新的add对象(注意此处add已经是一个可调用对象,而非函数,下文分析源码会看到)只需要接收一个参数即可。

通俗点说:就是把原函数的部分参数固定了初始值,新的调用只需要传递其它参数。

下面来分析partial的源码(Python3.7),只摘录了核心部分:

class partial:
    """New function with partial application of the given arguments
    and keywords.
    """

    __slots__ = "func", "args", "keywords", "__dict__", "__weakref__"

    def __new__(*args, **keywords):
        if not args:
            raise TypeError("descriptor '__new__' of partial needs an argument")
        if len(args) < 2:
            raise TypeError("type 'partial' takes at least one argument")
        cls, func, *args = args
        if not callable(func):
            raise TypeError("the first argument must be callable")
        args = tuple(args)

        if hasattr(func, "func"):
            args = func.args + args
            tmpkw = func.keywords.copy()
            tmpkw.update(keywords)
            keywords = tmpkw
            del tmpkw
            func = func.func

        self = super(partial, cls).__new__(cls)

        self.func = func
        self.args = args
        self.keywords = keywords
        return self

    def __call__(*args, **keywords):
        if not args:
            raise TypeError("descriptor '__call__' of partial needs an argument")
        self, *args = args
        newkeywords = self.keywords.copy()
        newkeywords.update(keywords)
        return self.func(*self.args, *args, **newkeywords)

通过重写“_new__”方法,自定义对象实例化过程。

1、元组拆包,获取到传入的原函数(func)和需要固定的参数(args)

cls, func, *args = args

2、主要是为了支持嵌套调用,即add=partial(partial(add,1),2)这种情况,可先看第三步,回过头再来看

if hasattr(func, "func"):
    args = func.args + args
    tmpkw = func.keywords.copy()
    tmpkw.update(keywords)
    keywords = tmpkw
    del tmpkw
    func = func.func

3、实例化partial对象,将传入的函数和参数设置为当前对象的属性

self = super(partial, cls).__new__(cls)

self.func = func
self.args = args
self.keywords = keywords
return self

到这里我们已经明白了partial是怎么保存原函数和固定参数的了,下面来看一下调用的时候是如何执行的。

先简单了解一下可调用对象:当一个类实现了"__call__"方法后,这个类的对象就能够像函数一样被调用。

class Callable:
    def __call__(self, a, b):
        return a + b


func = Callable()  
result = func(2, 3) # 像函数一样调用
print(result)

输出:5

好啦,我们看下partial的"__call__"是如何实现的:

def __call__(*args, **keywords):
        if not args:
            raise TypeError("descriptor '__call__' of partial needs an argument")
        self, *args = args
        newkeywords = self.keywords.copy()
        newkeywords.update(keywords)
        return self.func(*self.args, *args, **newkeywords)

1、元组拆包,获取到传入的非固定参数args

self, *args = args

2、拷贝当前对象的keywords参数,并且合并传入的非固定参数字典

newkeywords = self.keywords.copy()
newkeywords.update(keywords)

3、调用当前对象的func属性,func即被partial包装的原函数,同时传入暂存的固定参数self.args以及新传入的其它参数。

至此一切真相大白:partial通过实现"__new__"和"__call__"生成一个可调用对象,这个对象内部保存了被包装函数以及固定参数,这个对象可以像函数一样被调用,调用时,其实是执行了对象内部持有的被包装函数,其参数由固定参数和新传入的参数组合而来。、


3、reduce

与Python内置的reduce函数一样,为了向Python3过渡;

4、total_ordering

这个装饰器是在python2.7的时候加上的,它是针对某个类如果定义了lt、le、gt、ge这些方法中的至少一个,使用该装饰器,则会自动的把其他几个比较函数也实现在该类中
from functools import total_ordering

class Person:
    # 定义相等的比较函数
    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()))

p1 = Person()
p2 = Person()

p1.lastname = "123"
p1.firstname = "000"

p2.lastname = "1231"
p2.firstname = "000"

print p1 < p2  # True
print p1 <= p2  # True
print p1 == p2  # False
print p1 > p2  # False
print p1 >= p2  # False

5、update_wrapper

更新一个包裹(wrapper)函数,使其看起来更像被包裹(wrapped)的函数。

下面来看update_wrapper的源码:

def update_wrapper(wrapper,
                   wrapped,
                   assigned = WRAPPER_ASSIGNMENTS,
                   updated = WRAPPER_UPDATES):
    """Update a wrapper function to look like the wrapped function

       wrapper is the function to be updated
       wrapped is the original function
       assigned is a tuple naming the attributes assigned directly
       from the wrapped function to the wrapper function (defaults to
       functools.WRAPPER_ASSIGNMENTS)
       updated is a tuple naming the attributes of the wrapper that
       are updated with the corresponding attribute from the wrapped
       function (defaults to functools.WRAPPER_UPDATES)
    """
    for attr in assigned:
        try:
            value = getattr(wrapped, attr)
        except AttributeError:
            pass
        else:
            setattr(wrapper, attr, value)
    for attr in updated:
        getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
    # Issue #17482: set __wrapped__ last so we don't inadvertently copy it
    # from the wrapped function when updating __dict__
    wrapper.__wrapped__ = wrapped
    # Return the wrapper so this can be used as a decorator via partial()
    return wrapper

代码很简洁,就是把wrapped函数的属性拷贝到wrapper函数中。

wrapped是被装饰的原函数

wrapper是被装饰器装饰后的新函数。

6、wraps

这个函数可用作一个装饰器,简化调用update_wrapper的过程,调用这个函数等价于调用partial(update_wrapper, wrapped = wrapped, assigned = assigned,updated = updated)。

下面来看update_wrapper的源码:

def wraps(wrapped,
          assigned = WRAPPER_ASSIGNMENTS,
          updated = WRAPPER_UPDATES):
    """Decorator factory to apply update_wrapper() to a wrapper function

       Returns a decorator that invokes update_wrapper() with the decorated
       function as the wrapper argument and the arguments to wraps() as the
       remaining arguments. Default arguments are as for update_wrapper().
       This is a convenience function to simplify applying partial() to
       update_wrapper().
    """
    return partial(update_wrapper, wrapped=wrapped,
                   assigned=assigned, updated=updated)

wrapped:指被装饰器装饰的原函数,我们的装饰器便是要拷贝它的属性。

assigned:要被重新赋值的属性列表,默认为WRAPPER_ASSIGNMENTS,可自定义传入

WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
                       '__annotations__')

updated:要被合并的属性列表,默认为WRAPPER_UPDATES,可自定义传入

WRAPPER_UPDATES = ('__dict__',)

返回值:

返回了一个partial对象,这个对象对update_wrapper进行了包装,固定了wrapped,assigned,updated三个参数。

wraps本省就是一个装饰器,因为它返回的是一个“函数”即partial对象,这个对象接收函数作为参数,同时以函数作为返回值。

技术链接
原文地址:https://www.cnblogs.com/liujiacai/p/15612092.html