functools partial是怎么做的呢?

我不能得到我的头partial如何工作在functools。 下面是在这里中的代码:

>>> sum = lambda x, y : x + y
>>> sum(1, 2)
3
>>> incr = lambda y : sum(1, y)
>>> incr(2)
3
>>> def sum2(x, y):
return x + y


>>> incr2 = functools.partial(sum2, 1)
>>> incr2(4)
5

现在在队列中

incr = lambda y : sum(1, y)

我得到,无论我传递给incr的参数是什么,它都将作为y传递给lambda,后者将返回sum(1, y),即1 + y

我理解。但是我不明白这个incr2(4)

4如何在局部函数中作为x传递?对我来说,4应该取代sum2x4之间的关系是什么?

222887 次浏览

粗略地说,partial做了这样的事情(除了关键字参数支持等):

def partial(func, *part_args):
def wrapper(*extra_args):
args = list(part_args)
args.extend(extra_args)
return func(*args)


return wrapper

因此,通过调用partial(sum2, 4),你创建了一个新函数(准确地说,是一个可调用函数),它的行为类似sum2,但少了一个位置参数。缺少的参数总是被4代替,因此partial(sum2, 4)(2) == sum2(4, 2) . c

至于为什么需要它,有各种各样的情况。举个例子,假设你必须将一个函数传递到有两个参数的地方:

class EventNotifier(object):
def __init__(self):
self._listeners = []


def add_listener(self, callback):
''' callback should accept two positional arguments, event and params '''
self._listeners.append(callback)
# ...


def notify(self, event, *params):
for f in self._listeners:
f(event, params)

但是你已经拥有的函数需要访问第三个context对象来完成它的工作:

def log_event(context, event, params):
context.log_event("Something happened %s, %s", event, params)

因此,有几个解决方案:

自定义对象:

class Listener(object):
def __init__(self, context):
self._context = context


def __call__(self, event, params):
self._context.log_event("Something happened %s, %s", event, params)




notifier.add_listener(Listener(context))

λ:

log_listener = lambda event, params: log_event(context, event, params)
notifier.add_listener(log_listener)

分音:

context = get_context()  # whatever
notifier.add_listener(partial(log_event, context))
在这三个中,partial是最短和最快的。 (对于更复杂的情况,你可能需要一个自定义对象)

分音是非常有用的。

例如,在“管道式”函数调用序列中(其中一个函数的返回值是传递给下一个函数的参数)。

有时,这样的管道中的函数需要单参数,但从它直接上游的函数返回两个值

在这种情况下,functools.partial可能允许你保持这个函数管道完整。

这里有一个具体的孤立的例子:假设你想要根据每个数据点到某个目标的距离对一些数据进行排序:

# create some data
import random as RND
fnx = lambda: RND.randint(0, 10)
data = [ (fnx(), fnx()) for c in range(10) ]
target = (2, 4)


import math
def euclid_dist(v1, v2):
x1, y1 = v1
x2, y2 = v2
return math.sqrt((x2 - x1)**2 + (y2 - y1)**2)

要根据与目标的距离对这些数据进行排序,你需要做的当然是:

data.sort(key=euclid_dist)

但是你不能——排序方法的关键形参只接受带有实参的函数。

因此,将euclid_dist重写为一个带形参的函数:

from functools import partial


p_euclid_dist = partial(euclid_dist, target)

p_euclid_dist现在接受单个参数,

>>> p_euclid_dist((3, 3))
1.4142135623730951

所以现在你可以通过传入sort方法的key参数的partial函数来排序你的数据:

data.sort(key=p_euclid_dist)


# verify that it works:
for p in data:
print(round(p_euclid_dist(p), 3))


1.0
2.236
2.236
3.606
4.243
5.0
5.831
6.325
7.071
8.602

或者,例如,函数的一个参数在外部循环中发生了变化,但在内部循环的迭代中是固定的。通过使用partial,您不必在内部循环迭代期间传递额外的参数,因为修改后的(partial)函数不需要它。

>>> from functools import partial


>>> def fnx(a, b, c):
return a + b + c


>>> fnx(3, 4, 5)
12

创建一个局部函数(使用关键字arg)

>>> pfnx = partial(fnx, a=12)


>>> pfnx(b=4, c=5)
21

您还可以使用位置参数创建偏函数

>>> pfnx = partial(fnx, 12)


>>> pfnx(4, 5)
21

但这将抛出(例如,使用关键字参数创建partial,然后使用位置参数调用)

>>> pfnx = partial(fnx, a=12)


>>> pfnx(4, 5)
Traceback (most recent call last):
File "<pyshell#80>", line 1, in <module>
pfnx(4, 5)
TypeError: fnx() got multiple values for keyword argument 'a'

另一个用例:使用python的multiprocessing库编写分布式代码。使用pool方法创建进程池:

>>> import multiprocessing as MP


>>> # create a process pool:
>>> ppool = MP.Pool()

Pool有一个map方法,但它只接受一个可迭代对象,所以如果你需要传入一个具有更长的形参列表的函数,请将该函数重新定义为一个局部函数,以修复除一个以外的所有函数:

>>> ppool.map(pfnx, [4, 6, 7, 8])

偏导数可以用来生成一些预先赋值的输入参数的新派生函数

要了解一些真实世界中局部符号的用法,请参考这篇非常好的博客文章在这里

这是博客中一个简单但简洁的初学者示例,介绍了如何在re.search上使用partial使代码更具可读性。re.search方法的签名是:

search(pattern, string, flags=0)

通过应用partial,我们可以创建正则表达式search的多个版本来满足我们的需求,例如:

is_spaced_apart = partial(re.search, '[a-zA-Z]\s\=')
is_grouped_together = partial(re.search, '[a-zA-Z]\=')

现在is_spaced_apartis_grouped_together是两个从re.search派生的新函数,它们应用了pattern参数(因为patternre.search方法签名中的第一个参数)。

这两个新函数(可调用)的签名是:

is_spaced_apart(string, flags=0)     # pattern '[a-zA-Z]\s\=' applied
is_grouped_together(string, flags=0) # pattern '[a-zA-Z]\=' applied

这就是你如何在一些文本上使用这些partial函数:

for text in lines:
if is_grouped_together(text):
some_action(text)
elif is_spaced_apart(text):
some_other_action(text)
else:
some_default_action()

你可以参考上面的链接来更深入地了解这个主题,因为它涵盖了这个特定的例子和更多。

简单地说,partial为函数的参数提供了默认值,否则这些参数将没有默认值。

from functools import partial


def foo(a,b):
return a+b


bar = partial(foo, a=1) # equivalent to: foo(a=1, b)
bar(b=10)
#11 = 1+10
bar(a=101, b=10)
#111=101+10

在我看来,这是在python中实现局部套用的一种方法。

from functools import partial
def add(a,b):
return a + b


def add2number(x,y,z):
return x + y + z


if __name__ == "__main__":
add2 = partial(add,2)
print("result of add2 ",add2(1))
add3 = partial(partial(add2number,1),2)
print("result of add3",add3(1))

结果是3和4。

另外值得一提的是,当部分函数传递给另一个函数时,我们想要“硬编码”一些参数,那应该是最右边的参数

def func(a,b):
return a*b
prt = partial(func, b=7)
print(prt(4))
#return 28

但如果我们做同样的事情,只是改变一个参数

def func(a,b):
return a*b
prt = partial(func, a=7)
print(prt(4))

将抛出错误, "TypeError: func() got multiple values for argument 'a'"

这个答案更像是一个示例代码。以上所有答案都很好地解释了为什么应该使用partial。我将给出关于partial的观察和用例。

from functools import partial
def adder(a,b,c):
print('a:{},b:{},c:{}'.format(a,b,c))
ans = a+b+c
print(ans)
partial_adder = partial(adder,1,2)
partial_adder(3)  ## now partial_adder is a callable that can take only one argument

上述代码的输出应该是:

a:1,b:2,c:3
6

注意,在上面的例子中,返回了一个新的可调用对象,它将形参(c)作为参数。注意,它也是函数的最后一个参数。

args = [1,2]
partial_adder = partial(adder,*args)
partial_adder(3)

上述代码的输出也是:

a:1,b:2,c:3
6

注意,*用于解包非关键字参数,返回的可调用对象的参数与上面相同。

另一个观察结果是: 下面的示例演示了partial返回一个可调用对象,该对象将接受 未声明的参数(a)作为参数

def adder(a,b=1,c=2,d=3,e=4):
print('a:{},b:{},c:{},d:{},e:{}'.format(a,b,c,d,e))
ans = a+b+c+d+e
print(ans)
partial_adder = partial(adder,b=10,c=2)
partial_adder(20)

上述代码的输出应该是:

a:20,b:10,c:2,d:3,e:4
39

同样的,

kwargs = {'b':10,'c':2}
partial_adder = partial(adder,**kwargs)
partial_adder(20)

以上代码打印

a:20,b:10,c:2,d:3,e:4
39

当我从multiprocessing模块中使用Pool.map_async方法时,我必须使用它。你只能将一个参数传递给worker函数,所以我必须使用partial使我的worker函数看起来像一个只有一个输入参数的可调用对象,但实际上我的worker函数有多个输入参数。

从机器学习中添加了两个例子,其中函数式编程局部套用functools.partial非常有用:

在同一数据集上构建多个模型

下面的例子展示了如何在同一个diabetes数据集上拟合linear regressionsupport vector machinerandom forest回归模型,以预测目标并计算得分。

(部分)函数classify_diabetes()是通过curry(使用functools.partial())从函数classify_data()创建的。后面的函数不再需要传递数据,我们可以直接只传递模型类的实例。

from functools import partial
from sklearn.linear_model import LinearRegression
from sklearn.svm import SVR
from sklearn.ensemble import RandomForestRegressor
from sklearn.datasets import load_diabetes


def classify_data(data, model):
reg = model.fit(data['data'], data['target'])
return model.score(data['data'], data['target'])


diabetes = load_diabetes()
classify_diabetes = partial(classify_data, diabetes) # curry
for model in [LinearRegression(), SVR(), RandomForestRegressor()]:
print(f'model {type(model).__name__}: score = {classify_diabetes(model)}')


# model LinearRegression: score = 0.5177494254132934
# model SVR: score = 0.2071794500005485
# model RandomForestRegressor: score = 0.9216794155402649

建立机器学习管道

在这里,函数pipeline()是用curcuring创建的,它已经使用StandardScaler()在拟合模型之前对数据进行预处理(缩放/规范化),如下例所示:

from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler


pipeline = partial(make_pipeline, StandardScaler()) # curry
for model in [LinearRegression(), SVR(), RandomForestRegressor()]:
print(f"model {type(model).__name__}: " \
f"score = {pipeline(model).fit(diabetes['data'], diabetes['target'])\
.score(diabetes['data'], diabetes['target'])}")


# model LinearRegression: score = 0.5177494254132934
# model SVR: score = 0.2071794500005446
# model RandomForestRegressor: score = 0.9180227193805106