zoukankan      html  css  js  c++  java
  • (转)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对象,这个对象接收函数作为参数,同时以函数作为返回值。

    技术链接
  • 相关阅读:
    iOS 数据存储
    iOS 中@property() 括号中,可以填写的属性?
    iOS 中关闭键盘方法
    iBeacons 资源汇总
    iOS7 下去掉状态栏(全屏)
    监听器HttpSessionListener
    监听器 HttpSessionBindingListener
    servlet 3.0 的使用
    工厂模式小例子 getDaoImp
    servlet和filter初始化
  • 原文地址:https://www.cnblogs.com/liujiacai/p/15612092.html
Copyright © 2011-2022 走看看