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

Nic*_*ner 183 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
Run Code Online (Sandbox Code Playgroud)

functools某种程度上更有效,或可读?

Ale*_*lli 247

functools.partial提供的功能是什么,你无法通过lambdas?

在额外功能方面并不多(但是,请参阅后面的内容) - 而且,可读性在旁观者眼中.
大多数熟悉函数式编程语言的人(特别是Lisp/Scheme系列中的人)似乎都很喜欢lambda- 我说"最",绝对不是全部,因为Guido和我肯定是那些"熟悉"的人(但是lambda在Python中
却被认为是眼中的异常... 他曾悔改过将其接受到Python中,并计划将其从Python 3中删除,作为"Python的故障"之一.
我完全支持他.(我喜欢lambda Scheme ...虽然它在Python中的局限性,以及它的奇怪方式' 用其他语言,让我的皮肤爬行).

事实并非如此,但是,成群的lambda爱好者-谁上演最亲密的事情见过Python的历史叛乱之一,直到圭多回溯和决定离开lambda,在
几个可能的补充functools(使函数返回的常量,身份,没有发生(为了避免明确地复制更多lambda的功能),虽然partial当然仍然存在(它不是完全重复,也不是一个眼睛).

请记住,lambda身体仅限于表达,因此它有局限性.例如...:

>>> import functools
>>> f = functools.partial(int, base=2)
>>> f.args
()
>>> f.func
<type 'int'>
>>> f.keywords
{'base': 2}
>>> 
Run Code Online (Sandbox Code Playgroud)

functools.partial返回的函数装饰有对内省有用的属性 - 它包装的函数,以及它在其中修复的位置和命名参数.此外,可以在右后方覆盖命名参数(在某种意义上,"修复"是默认设置):

>>> f('23', base=10)
23
Run Code Online (Sandbox Code Playgroud)

所以,如你所见,它的定义并不像lambda s: int(s, base=2)! - )

是的,你可以扭曲你的lambda给你一些 - 例如,关键字覆盖,

>>> f = lambda s, **k: int(s, **dict({'base': 2}, **k))
Run Code Online (Sandbox Code Playgroud)

但是我非常希望即使是最热情的lambda人也不会认为这种恐怖比partial电话更具可读性! - )."属性设置"部分更难,因为Python的"body是单一表达式"限制lambda(加上赋值永远不能成为Python表达式的一部分)...最终"假装在表达式中赋值"通过扩展列表理解远远超出其设计限制......:

>>> f = [f for f in (lambda f: int(s, base=2),)
           if setattr(f, 'keywords', {'base': 2}) is None][0]
Run Code Online (Sandbox Code Playgroud)

现在结合命名参数覆盖性,再加上三个属性的设置,到一个单一的表达,并告诉我是多么可读将是... - !)

  • @Rosarch,正如我所说的那样:首先,它有局限性(Python明显地区分了表达式和语句 - 在单个表达式中你有很多不能做或不能_sensibly _,这就是lambda的主体_is_); 第二,它绝对奇怪的语法糖.如果我能及时回过头来改变Python中的一件事,那就是荒谬的,毫无意义的,眼睛`def`和`lambda`关键词:让它们成为`function`(一个名字选择Javascript得到_really_ right),并且至少有1/3的反对意见会消失! - ).正如我所说,我不反对lambda _in Lisp _...! - ) (11认同)
  • @AlexMartelli DropBox对Guido产生了有趣的影响 - https://twitter.com/gvanrossum/status/391769557758521345 (5认同)
  • @PeterLong希望[Guido](http://www.artima.com/weblogs/viewpost.jsp?thread=147358)可以回答你的问题.它的要点是它太复杂了,无论如何你都可以使用`def`.我们仁慈的领导人已经说过了! (3认同)
  • 是的,我会说你提到的 `functools.partial` 的额外功能使它优于 lambda。也许这是另一篇文章的主题,但在设计层面上,关于 `lambda` 是什么让你如此烦恼? (2认同)
  • +1但复杂的 lambda 示例有损于答案:lambda 也可以有默认参数:`f = lambda s, base=2: int(s, base=base)`。 (2认同)

ars*_*ars 75

嗯,这是一个显示差异的例子:

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
Run Code Online (Sandbox Code Playgroud)

Ivan Moore的这些帖子扩展了"lambda的限制"和python中的闭包:

  • 这种"早期与晚期结合困境"的解决方法是明确地使用早期绑定,当你想要时,通过`lambda y,n = n:...`.后期绑定(在函数体中出现_only_的名称,而不是在其`def`或等效的`lambda`中)是****但是**是一个错误,因为我在过去的长期SO答案中已经详细显示了:你当你想要的时候明确地进行早期绑定,当*that*就是你想要的时候使用后期绑定默认值,并且在给定Python设计的其余部分的上下文的情况下,_exactly_是正确的设计选择. (28认同)
  • 亚历克斯是对的,这不是一个错误.但这是一个困扰许多lambda爱好者的"陷阱".对于来自haskel/functional类型的参数的"bug",请参阅Andrej Bauer的帖子:http://math.andrej.com/2009/04/09/pythons-lambda-is-broken/ (3认同)
  • @Alex Martelli:是的,抱歉。我只是没能正确地习惯后期绑定,也许是因为我认为在定义函数时,我实际上是在永久定义一些东西,而意想不到的惊喜只会让我头疼。(不过,当我尝试在 Javascript 中执行功能性操作时,比在 Python 中执行的操作要多。)我知道很多人都对后期绑定感到满意,并且它与 Python 的其他设计是一致的。不过,我仍然想阅读您其他很长的答案——链接?:-) (2认同)

Fre*_*Foo 26

在最新版本的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>
Run Code Online (Sandbox Code Playgroud)

  • @wting那篇文章来自2010年.在Python 2.7中,`partial`是pickleable. (3认同)

Tri*_*ion 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))
Run Code Online (Sandbox Code Playgroud)

在Python 3.3上它给出:

lambda creation time 3.1743163756961392
lambda execution time 3.040552701787919
partial creation time 3.514482823352731
partial execution time 1.7113973411608114
Run Code Online (Sandbox Code Playgroud)

这意味着部分需要更多的时间来创建,但执行的时间要少得多.这很可能是早期和晚期结合的影响,这在ars的答案中讨论过.

  • 更重要的是,`partial`是用C语言编写的,而不是纯Python,这意味着它可以产生比简单地创建调用另一个函数的函数更高效的可调用性. (2认同)

Leo*_*o.Z 11

除了Alex提到的额外功能外,functools.partial的另一个优点是速度.使用partial,您可以避免构造(和破坏)另一个堆栈帧.

部分生成的函数从原始函数继承docstring,而lambdas默认没有docstrings(尽管你可以通过设置任何对象的doc字符串__doc__)

您可以在此博客中找到更多详细信息:Python中的部分功能应用程序


Jon*_*ric 1

我在第三个例子中最快理解了意图。

当我解析 lambda 时,我期望比标准库直接提供的更复杂/奇怪。

另外,您会注意到第三个示例是唯一一个不依赖于sum2;的完整签名的示例。从而使其耦合更加松散。

  • 嗯,我实际上持相反的观点,我花了更长的时间来解析 `functools.partial` 调用,而 lambda 是不言而喻的。 (2认同)