问题:functools部分如何做?
我无法了解部分功能在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
应该更换sum2
。x
和之间是什么关系4
?
回答 0
大致地,partial
做这样的事情(除了关键字args支持等):
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)
至于为什么需要它,有很多情况。仅举一个例子,假设您必须在某个有两个参数的地方传递一个函数:
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))
Lambda:
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
最短和最快。(对于更复杂的情况,您可能需要自定义对象)。
回答 1
局部函数非常有用。
例如,在“管线式”函数调用序列中(其中一个函数的返回值是传递给下一个函数的参数)。
有时,此类管道中的函数需要单个参数,但是紧接其上游的函数将返回两个值。
在这种情况下,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参数的局部函数来对数据进行排序:
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
又例如,函数的参数之一在外循环中更改,但在内循环迭代期间是固定的。通过使用部分函数,您无需在内部循环的迭代过程中传递其他参数,因为修改后的(部分函数)不需要此参数。
>>> 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])
回答 2
简短的答案,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
回答 3
可以使用部分函数来创建新的派生函数,这些函数具有预先分配的一些输入参数
要了解部分用法在现实世界中的用法,请参阅此非常好的博客文章:http :
//chriskiehl.com/article/Cleaner-coding-through-partially-applied-functions/
博客中的一个简单但简洁的示例,介绍了如何使用partial
它re.search
使代码更具可读性。 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_apart
和is_grouped_together
是从中派生的两个新函数re.search
,它们pattern
应用了自变量(因为它pattern
是re.search
方法签名中的第一个自变量)。
这两个新函数(可调用)的签名为:
is_spaced_apart(string, flags=0) # pattern '[a-zA-Z]\s\=' applied
is_grouped_together(string, flags=0) # pattern '[a-zA-Z]\=' applied
这样便可以在某些文本上使用这些部分函数:
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()
您可以参考上面的链接,以更深入地了解该主题,因为它涵盖了此特定示例以及更多内容。
回答 4
我认为,这是在python中实现currying的一种方式。
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。
回答 5
还值得一提的是,当部分函数传递了另一个我们要“硬编码”某些参数的函数时,该参数应该是最右边的参数。
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()为参数’a’获得了多个值”
回答 6
这个答案更多是示例代码。上面的所有答案都很好地解释了为什么应该部分使用。我将给出我的观察和有关局部的用例。
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
注意,在上面的示例中,返回了一个新的callable,它将参数(c)作为其参数。请注意,它也是函数的最后一个参数。
args = [1,2]
partial_adder = partial(adder,*args)
partial_adder(3)
上面代码的输出也是:
a:1,b:2,c:3
6
请注意,*用于解压缩非关键字参数,而返回的callable可以接受的参数与上面相同。
另一个观察结果是: 下面的示例演示了partial返回一个callable,它将以未声明的参数(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
当我使用模块中的Pool.map_async
方法时,我不得不使用它multiprocessing
。您只能将一个参数传递给worker函数,因此我不得不使用它partial
来使我的worker函数看起来像只有一个输入参数的可调用对象,但实际上我的worker函数具有多个输入参数。