How does functools partial do what it does?
我无法理解functools中的部分工作原理。
我从这里得到以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 | >>> 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 |
现在就行了
1 | incr = lambda y : sum(1, y) |
我得到的是,无论我传递给
我明白那个。 但我不明白这个
粗略地说,
1 2 3 4 5 6 7 | def partial(func, *part_args): def wrapper(*extra_args): args = list(part_args) args.extend(extra_args) return func(*args) return wrapper |
因此,通过调用
至于为什么需要,有各种各样的案例。只是为了一个,假设你必须在一个预期有2个参数的地方传递一个函数:
1 2 3 4 5 6 7 8 9 10 11 12 | 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) |
但是,您已经拥有的函数需要访问某些第三个
1 2 | def log_event(context, event, params): context.log_event("Something happened %s, %s", event, params) |
所以,有几种解决方案:
自定义对象:
1 2 3 4 5 6 7 8 9 | 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:
1 2 | log_listener = lambda event, params: log_event(context, event, params) notifier.add_listener(log_listener) |
有部分:
1 2 | context = get_context() # whatever notifier.add_listener(partial(log_event, context)) |
在这三个中,
(对于更复杂的情况,您可能需要自定义对象)。
部分非常有用。
例如,在'pipe-lined'函数调用序列中(其中一个函数的返回值是传递给下一个函数的参数)。
有时这种管道中的函数需要一个参数,但紧接其上游的函数返回两个值。
在这种情况下,
这是一个特定的,孤立的示例:假设您希望按每个数据点与某个目标的距离对某些数据进行排序:
1 2 3 4 5 6 7 8 9 10 11 | # 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) |
要按目标距离对数据进行排序,您当然希望这样做:
1 | data.sort(key=euclid_dist) |
但你不能 - sort方法的key参数只接受带有一个参数的函数。
所以重写
1 2 3 | from functools import partial p_euclid_dist = partial(euclid_dist, target) |
1 2 | >>> p_euclid_dist((3, 3)) 1.4142135623730951 |
所以现在你可以通过传入sort方法的key参数的partial函数来对数据进行排序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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,您不必在内循环的迭代期间传递附加参数,因为修改的(部分)函数不需要它。
1 2 3 4 5 6 7 | >>> from functools import partial >>> def fnx(a, b, c): return a + b + c >>> fnx(3, 4, 5) 12 |
创建部分函数(使用关键字arg)
1 2 3 4 | >>> pfnx = partial(fnx, a=12) >>> pfnx(b=4, c=5) 21 |
您还可以使用位置参数创建部分函数
1 2 3 4 | >>> pfnx = partial(fnx, 12) >>> pfnx(4, 5) 21 |
但这将抛出(例如,创建部分关键字参数然后使用位置参数调用)
1 2 3 4 5 6 7 | >>> 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的
1 2 3 4 | >>> import multiprocessing as MP >>> # create a process pool: >>> ppool = MP.Pool() |
1 | >>> ppool.map(pfnx, [4, 6, 7, 8]) |
部分可用于创建预先分配了一些输入参数的新派生函数
要查看部分实际使用情况,请参阅这篇非常好的博文:
http://chriskiehl.com/article/Cleaner-coding-through-partially-applied-functions/
博客中一个简单而又整洁的初学者示例,介绍了如何在
1 | search(pattern, string, flags=0) |
通过应用
1 2 | is_spaced_apart = partial(re.search, '[a-zA-Z]\s\=') is_grouped_together = partial(re.search, '[a-zA-Z]\=') |
现在
这两个新函数(可调用)的签名是:
1 2 | is_spaced_apart(string, flags=0) # pattern '[a-zA-Z]\s\=' applied is_grouped_together(string, flags=0) # pattern '[a-zA-Z]\=' applied |
这就是你可以在某些文本上使用这些部分函数的方法:
1 2 3 4 5 6 7 | 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() |
您可以参考上面的链接,以更深入地了解该主题,因为它涵盖了这个具体的例子等等。
简短回答,
1 2 3 4 5 6 7 8 9 10 | 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中实现currying的方法。
1 2 3 4 5 6 7 8 9 10 11 12 | 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。
另外值得一提的是,当部分函数传递另一个函数时,我们想要"硬编码"一些参数,那应该是最右边的参数
1 2 3 4 5 | def func(a,b): return a*b prt = partial(func, b=7) print(prt(4)) #return 28 |
但是如果我们这样做,而是改变一个参数
1 2 3 4 | def func(a,b): return a*b prt = partial(func, a=7) print(prt(4)) |
它会抛出错误,
"TypeError:func()获得了参数'a'的多个值"