functools如何在Python中部分工作?

我无法让我的头如何在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) 

我得到,无论我传递给它的参数将被传递为ylambda ,这将返回sum(1, y)1 + y

我明白那个。 但我不明白这个incr2(4)

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

粗略地说, 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)你可以创build一个像sum2那样sum2的新函数(一个可调用的函数),但是只有一个位置参数。 这个缺less的参数总是被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) 

所以,有几个解决scheme:

自定义对象:

 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是最短和最快的。 (对于更复杂的情况你可能需要一个自定义对象)。

部分是非常有用的。

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

有时候,这样的pipe道中的函数需要一个参数 ,但是紧靠其上游的函数返回两个值

在这种情况下, functools.partial可能允许你保持这个函数pipe道不变。

这是一个特定的,孤立的例子:假设你想按照每个数据点与某个目标的距离来sorting一些数据:

 # 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) 

要按照距目标的距离对数据进行sorting,您想要做的事情当然是这样的:

 data.sort(key=euclid_dist) 

但你不能 – sorting方法的关键参数只接受只有一个参数的函数。

所以重写euclid_dist作为一个函数采取一个单一的参数:

 from functools import partial p_euclid_dist = partial(euclid_dist, target) 

现在p_euclid_dist接受一个参数,

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

所以现在您可以通过传入sorting方法的键参数的部分函数来对数据进行sorting:

 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,在迭代内部循环期间不必传入附加参数,因为修改的(部分)函数不需要它。

 >>> from functools import partial >>> def fnx(a, b, c): return a + b + c >>> fnx(3, 4, 5) 12 

创build一个部分函数(使用关键字arg)

 >>> pfnx = partial(fnx, a=12) >>> pfnx(b=4, c=5) 21 

你也可以创build一个带有位置参数的部分函数

 >>> pfnx = partial(fnx, 12) >>> pfnx(4, 5) 21 

但是这会抛出(例如,创build部分与关键字参数,然后调用使用位置参数)

 >>> 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方法创build一个进程池:

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

Pool有一个映射方法,但它只需要一个迭代器,所以如果你需要传入一个具有更长参数列表的函数,重新定义函数作为一个部分,来修复除了一个之外的所有东西:

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

这里的例子很好,但我想看到一些更现实世界的部分使用,并发现这个非常好的博客文章。
这可能有助于那些寻找更多的例子/现实世界的用法。 http://chriskiehl.com/article/Cleaner-coding-through-partially-applied-functions/

博客中一个简单而又整齐的初学者例子,介绍了如何使用re.search partial代码来使代码更具可读性。
re.search方法的签名是search(pattern, string, flags=0)

通过应用partial我们可以创build多个版本的正则expression式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方法签名中的第一个参数)。

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

 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() 

你可以参考上面的链接来获得对这个主题的更深入的理解,因为它涵盖了这个具体的例子等等。

简短的答案, partial给一个函数的参数默认值,否则将没有默认值。

 from functools import partial def foo(a,b,c,d): return a+b+c+d bar = partial(foo, a=1, b=2) bar(c=10, d=20) #33 = 1+2+10+20 bar(a=101, c=10, d=20) #133=101+2+10+20 
Interesting Posts