Python:为什么functools.partial是必要的?

时间:2010-07-15 03:16:11

标签: python functional-programming partial-application

部分申请很酷。 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是否更有效率或可读性?

6 个答案:

答案 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的完整签名的示例;从而使它稍微松散地耦合。