部分申请很酷。 functools.partial
提供了哪些功能无法通过lambdas?
>>> 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
functools
是否更有效率或可读性?
答案 0 :(得分:241)
有什么功能 functools.partial提供你不能 通过lambdas?
在额外的功能方面并不多(但是,请参阅后面的内容) - 而且,可读性在旁观者眼中。
大多数熟悉函数式编程语言的人(特别是Lisp / Scheme系列中的人)似乎都很喜欢lambda
- 我说“最”,绝对不是全部,因为Guido和我肯定是那些“熟悉”(等)的人之一,但我认为lambda
是Python中的眼睛异常......
他曾悔改过将其接受到Python中,并计划将其从Python 3中删除,作为“Python的故障”之一。
我完全支持他。 (我喜欢Scheme 中的lambda
...而它在Python中的限制 ,以及不适合的奇怪方式>使用其他语言,让我的皮肤爬行。)
然而,对于成群的lambda
爱好者来说并非如此 - 他们曾在Python的历史中找到了最接近叛乱的东西之一,直到Guido回归并决定离开lambda
。
functools
的几个可能的添加(使函数返回常量,标识等)没有发生(为了避免明确地复制lambda
的更多功能),尽管partial
当然做了保持(它没有总重复,也不是一个眼睛)。
请记住,lambda
的正文仅限于表达式,因此它有局限性。例如......:
>>> import functools
>>> f = functools.partial(int, base=2)
>>> f.args
()
>>> f.func
<type 'int'>
>>> f.keywords
{'base': 2}
>>>
functools.partial
的返回函数装饰有对内省有用的属性 - 它包装的函数,以及它在其中修复的位置和命名参数。此外,可以在右后方覆盖命名参数(在某种意义上,“修复”是默认设置):
>>> f('23', base=10)
23
所以,正如你所看到的那样, definely 并不像lambda s: int(s, base=2)
那么简单! - )
是的,你可以扭曲你的lambda给你一些 - 例如,关键字覆盖,
>>> f = lambda s, **k: int(s, **dict({'base': 2}, **k))
但我非常希望即使是最热情的lambda
- 情人也不会认为这个恐怖比partial
电话更具可读性! - )。 “属性设置”部分甚至更难,因为Python的lambda
的“正文是单一表达式”限制(加上赋值永远不能成为Python表达式的一部分)...你最终“假装”表达式中的赋值“通过拉伸列表理解远远超出其设计限制......:
>>> f = [f for f in (lambda f: int(s, base=2),)
if setattr(f, 'keywords', {'base': 2}) is None][0]
现在将命名参数可覆盖性加上三个属性的设置合并到一个表达式中,然后告诉我 的可读性是什么......! - )
答案 1 :(得分:69)
嗯,这是一个显示差异的例子:
In [132]: sum = lambda x, y: x + y
In [133]: n = 5
In [134]: incr = lambda y: sum(n, y)
In [135]: incr2 = partial(sum, n)
In [136]: print incr(3), incr2(3)
8 8
In [137]: n = 9
In [138]: print incr(3), incr2(3)
12 8
Ivan Moore的这些帖子扩展了“lambda的局限性”和python中的闭包:
答案 2 :(得分:25)
在最新版本的Python(&gt; = 2.7)中,您可以pickle
一个partial
,但不能lambda
:
>>> pickle.dumps(partial(int))
'cfunctools\npartial\np0\n(c__builtin__\nint\np1\ntp2\nRp3\n(g1\n(tNNtp4\nb.'
>>> pickle.dumps(lambda x: int(x))
Traceback (most recent call last):
File "<ipython-input-11-e32d5a050739>", line 1, in <module>
pickle.dumps(lambda x: int(x))
File "/usr/lib/python2.7/pickle.py", line 1374, in dumps
Pickler(file, protocol).dump(obj)
File "/usr/lib/python2.7/pickle.py", line 224, in dump
self.save(obj)
File "/usr/lib/python2.7/pickle.py", line 286, in save
f(self, obj) # Call unbound method with explicit self
File "/usr/lib/python2.7/pickle.py", line 748, in save_global
(obj, module, name))
PicklingError: Can't pickle <function <lambda> at 0x1729aa0>: it's not found as __main__.<lambda>
答案 3 :(得分:21)
functools是否更有效率??
作为部分答案,我决定测试性能。这是我的例子:
from functools import partial
import time, math
def make_lambda():
x = 1.3
return lambda: math.sin(x)
def make_partial():
x = 1.3
return partial(math.sin, x)
Iter = 10**7
start = time.clock()
for i in range(0, Iter):
l = make_lambda()
stop = time.clock()
print('lambda creation time {}'.format(stop - start))
start = time.clock()
for i in range(0, Iter):
l()
stop = time.clock()
print('lambda execution time {}'.format(stop - start))
start = time.clock()
for i in range(0, Iter):
p = make_partial()
stop = time.clock()
print('partial creation time {}'.format(stop - start))
start = time.clock()
for i in range(0, Iter):
p()
stop = time.clock()
print('partial execution time {}'.format(stop - start))
在Python 3.3上它给出了:
lambda creation time 3.1743163756961392
lambda execution time 3.040552701787919
partial creation time 3.514482823352731
partial execution time 1.7113973411608114
这意味着部分需要更多的时间来创建,但执行时间要少得多。这很可能是早期和晚期绑定的影响,这在ars的答案中讨论过。
答案 4 :(得分:11)
除了Alex提到的额外功能外,functools.partial的另一个优点是速度。使用partial,您可以避免构造(和破坏)另一个堆栈帧。
默认情况下,partial和lambdas生成的函数都没有docstrings(尽管你可以通过__doc__
为任何对象设置doc字符串。)
您可以在此博客中找到更多详细信息:Partial Function Application in Python
答案 5 :(得分:1)
我理解第三个例子中最快的意图。
当我解析lambda时,我期待比标准库直接提供更多的复杂性/奇怪性。
另外,您会注意到第三个示例是唯一一个不依赖于sum2
的完整签名的示例;从而使它稍微松散地耦合。