问题:Python:为什么functools.partial是必需的?
部分应用程序很酷。哪些功能functools.partial
提供了lambda无法获得的功能?
>>> 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
哪些功能
functools.partial
提供了lambda无法获得的功能?
在额外功能方面并没有太多(但是,请参阅稍后)–旁观者眼中的可读性。
大多数熟悉函数式编程语言的人(尤其是Lisp / Scheme系列的人)看起来都lambda
很好–我说“大多数”,绝对不是全部,因为Guido和我肯定是“熟悉”的人(等)
却被认为是lambda
Python中的一种令人眼花an乱的异常……
他为曾经接受过Python而打算将其从Python 3中删除(作为“ Python的小故障”之一)感到re悔。
我对此表示完全支持。(我喜欢lambda
Scheme,但是它在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
因此,如您所见,它绝对不像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
好吧,这是一个显示差异的示例:
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
在最新版本的Python(> = 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
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
这意味着partial需要更多的时间来创建,但是执行的时间却要少得多。这很可能是ars的答案中讨论的早期绑定和后期绑定的效果。
回答 4
除了Alex提到的额外功能之外,functools.partial的另一个优点是速度。使用partial,您可以避免构造(和破坏)另一个堆栈框架。
默认情况下,由partial和lambda生成的函数都没有文档字符串(尽管您可以通过以下方式为任何对象设置文档字符串 __doc__
)。
您可以在此博客中找到更多详细信息:Python中的部分函数应用程序
回答 5
我在第三个示例中最快地了解了意图。
当我解析lambda时,我期望比直接由标准库提供的复杂性/奇数更高。
另外,您会注意到,第三个示例是唯一一个不依赖于sum2
; 的完整签名的示例。因此使其耦合松散一些。