ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

偏函数partial

2022-08-01 16:02:21  阅读:128  来源: 互联网

标签:__ partial 函数 self args func keywords


前言

引入例子

from functools import partial

def demo(x, y, z):
    print(x,y,z)

new_demo = partial(demo,1)
new_demo(2,3)

输出:1 2 3

直观感受: 就是返回一个已经固定了部分参数和原函数功能一样的函数

再次举例:

new_demo = partial(demo,x=2)
new_demo(2,3)  #TypeError: demo() got multiple values for argument 'x' 报错,重复参数 x 

思考: 可能是因为已经对x定义为关键字参数,所以后续的y和z也必须为关键字参数【位置参数必须在关键字参数之前】,相当于demo(x=1,2,3)但是报错应该是:SyntaxError:positional argument follows keyword argument 测试如下:

def test(a,b):
    pass
# 关键字参数在位置参数前面,报错如下
test(a=1,2)

报错:SyntaxError: positional argument follows keyword argument

如果是报错重复参数的话,应该是如下这种情况:

def test(a,b):
    pass

test(1,a=2)

报错:TypeError: test() got multiple values for argument 'a'

所以偏函数的机制没有那么简单,看看下述源码分析


分析

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

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

    def __new__(cls, func, /, *args, **keywords):
        if not callable(func):
            raise TypeError("the first argument must be callable")

        if hasattr(func, "func"):
            args = func.args + args
            keywords = {**func.keywords, **keywords}
            func = func.func

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

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

    def __call__(self, /, *args, **keywords):
        keywords = {**self.keywords, **keywords}
        return self.func(*self.args, *args, **keywords)

构造方法分析

形参中的/

/ 前的参数,只能以位置参数来传递,不能以关键字形式传递

def test(a,b,/,c):
    pass

test(1,2,c=3)
test(1,b=2,c=3) # 报错: TypeError: test() got some positional-only arguments passed as keyword arguments: 'b'

所以只能通过位置参数传递需要“偏函数处理的func”给构造方法,如果是通过键值对形式传递func参数会报错如下:

def func(a,b):
    pass

partial(func=func,a=1)
#TypeError: type 'partial' takes at least one argument
#因为这里的func=func,a=1 会被当做构造方法的 **kwargs参数接受

partial(func,a=1) #这么写就不会报错,必须通过位置参数来传递

第一个if

第一个判断:如果传递进去的func不是callable的,报错,举例如下:

from functools import partial

demo = 1

partial(demo) #TypeError: the first argument must be callable

所以,能partial的不仅仅是函数,只要是能调用的 都是能进行处理的,比如一个类,实现了 __call__方法

第二个if

if hasattr(func, "func"):这里是对已经partial处理过一次的对象,再次进行partial处理时所做的逻辑,在嵌套进行偏函数处理,即:partial的func参数接收的是一个partial实例的时候,把两次显示声明固定的参数组合起来

后续逻辑

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

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

构造方法 返回partial实例,赋予 实例属性 func/args/keywords,测试如下:

from functools import partial

def func(a,b,c):
    pass

obj = partial(func,1,b=2)
print(obj)#functools.partial(<function func at 0x000001F1A84DEF70>, 1, b=2)
print(obj.func)#<function func at 0x000001F1A84DEF70>
print(obj.args)#(1,)
print(obj.keywords){'b': 2}

此时输出obj.__dict__是一个空字典,因为partial定义了__slots__

call方法

    def __call__(self, /, *args, **keywords):
        # 去重,所以partial 对象调用的时候 ()参数能传递 声明时候相同的键值对 但是不能多传位置参数
        ##  关键字参数 ,重新解包打包的字典,call调用的关键字参数放在后面,所以能覆盖之前字典中 同名的key的value
        ##  但是前面的self.args 这种就不能重新传入了,会报错
        keywords = {**self.keywords, **keywords}
        return self.func(*self.args, *args, **keywords)

关键字参数keywords: self.keywords是诸如实例化时res = partial(func,a=1,b=2) 对应的 {'a':1,'b':2},如果 实例化返回的partial对象res调用的时候再次传入键值对参数res(b=3),这时候__call__中的第一行相当于 keywords = {**{'a':1,'b':2},**{'b':3}}, 双星号 解包之后,相当于最后keywords = {'a':1,'b':3} ,所以在res调用的时候是可以重新传递前面固定的关键字参数,但是不能重新传递前面固定的位置参数,因为return self.func(*self.args, *args, **keywords)这里已经把之前固定的self.args放置好,回到一开始前言中的问题,这就是为什么会报错参数重复了

举例

partial实例调用时,可以重新传递已经固定好的关键字参数,但是重新传递时,只能按照关键字参数传递

def add(x,y):
    return x+y

new_add = partial(add,y=2)
print(new_add(1))
# print(new_add(1,2)) # 会报错,重复了y,看call中的**keywords已经为y=2了,这时候args 还传递了两个参数,所以重复了
print(new_add(1,y=2))# 重新传递正常执行

调用时,重新传递位置参数会报错

def add(x,y):
    return x+y

new_add = partial(add,1) # 传递位置参数给x =1 

new_add(2,3)      # TypeError: add() takes 2 positional arguments but 3 were given
new_add(2,y=3)    # TypeError: add() got multiple values for argument 'y'
new_add(x=2,y=3)  # TypeError: add() got multiple values for argument 'x'

传递的func参数不一定必须是函数,只要实现了__call__方法

class Demo:

    def __call__(self,x):
        return x
    
par_obj = partial(Demo(),100)
print(par_obj()) #输出100

传递的func参数为partial对象,链式调用

def test(a,b,c,d):
    print(a,b,c,d)


obj1 = partial(test,1)

obj2 = partial(obj1,2)

obj3 = partial(obj2,c=3)

# obj3(4)#报错,参数c重复,还是要根据函数的参数逻辑来,关键字参数要在位置参数之后

obj3(d=4)  # 输出 1 2 3 4

走一边源码流程

    def __new__(cls, func, /, *args, **keywords):
        if not callable(func):
            raise TypeError("the first argument must be callable")

        if hasattr(func, "func"):
            args = func.args + args
            keywords = {**func.keywords, **keywords}
            func = func.func

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

        #创建一个实例属性 保存原来的函数
        self.func = func
        # 保存调用partial时候 传进来的位置参数
        self.args = args
        # 保存调用partial时候 传进来的关键字参数
        self.keywords = keywords
        return self
    def __call__(self, /, *args, **keywords):
        keywords = {**self.keywords, **keywords}
        return self.func(*self.args, *args, **keywords)

obj1 的 func = test函数, self.args = (1,) kwargs={}

obj2 的func = obj1,因为if hasattr(obj1, "func")为true,所以self.args=obj1.args + 2,self.args = (1,2),kwargs={} 然后func = func.func原始的函数test赋值过来

同理 obj3 的self.args = (1,2),self.kwargs={'c':3}

所以obj3(d=4) 相当于 *self.args为(1,2) **self.keywords为{'c':3} **keywords为{'d':4}keywordsz再组合成{'c':3,'d':4}, 最后输出结果为 1 2 3 4

标签:__,partial,函数,self,args,func,keywords
来源: https://www.cnblogs.com/alantammm/p/16540607.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有