偏函数partial
作者:互联网
前言
引入例子
:
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