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)
我得到,无论我传递给它的参数将被传递为y
到lambda
,这将返回sum(1, y)
即1 + y
。
我明白那个。 但我不明白这个incr2(4)
。
4
如何在部分函数中作为x
传递? 对我来说, 4
应该取代sum2
。 x
和4
之间的关系是什么?
粗略地说, 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_apart
和is_grouped_together
是从re.search
派生的两个新函数,它们应用了pattern
参数(因为pattern
是re.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