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


193

部分应用程序很酷。哪些功能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某种方式更有效或更可读吗?

Answers:


265

哪些功能functools.partial提供了lambda无法获得的功能?

在额外功能方面并没有太多(但是,请参阅稍后)–旁观者眼中的可读性。
大多数熟悉函数式编程语言的人(尤其是Lisp / Scheme系列的人)看起来都lambda很好–我说“大多数”,绝对不是全部,因为Guido和我肯定是“熟悉”的人(等) 却被认为是lambdaPython中的一种令人眼花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]

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


2
是的,我想说的functools.partial是您提到的额外功能使其优于lambda。也许这是另一篇文章的主题,但是在设计层面上让您感到困扰的是什么lambda
尼克·海纳

11
@Rosarch,正如我所说的:第一,它的局限性(Python的大幅区分表达式和语句-有多少你不能这样做,或不能做什么理智,一个表达式中,而这正是一个lambda的身体); 其次,它的语法语法绝对不可思议。如果我能回到过去并在Python中更改一件事,那将是荒谬,毫无意义的,令人讨厌的deflambda关键字:将它们都设为function(一个名称选择Javascript 确实正确),并且至少有1/3的反对意见将消失!-)。正如我所说,我不反对Lisp中的 lambda ...!-)
Alex Martelli

1
@Alex Martelli,Guido为什么对lambda设置了这样一个限制:“身体是一个单一的表达”?C#的lambda主体可以是函数主体中任何有效的东西。Guido为什么不删除python lambda的限制?
彼得·朗

3
@PeterLong希望Guido可以回答您的问题。其要点是它太复杂了,def无论如何都可以使用。我们的仁慈领袖已经发言!
new123456'2

5
@AlexMartelli的Dropbox已经对圭多一个有趣的影响- twitter.com/gvanrossum/status/391769557758521345
大卫

82

好吧,这是一个显示差异的示例:

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中的闭包:


1
好的例子。在我看来,这实际上更像是lambda的“错误”,但我知道其他人可能会不同意。(用几种编程语言实现的闭包中定义的闭包也发生了类似的情况。)
ShreevatsaR 2010年

28
解决“早期绑定与后期绑定难题”的方法是,在需要时通过显式使用早期绑定lambda y, n=n: ...。后期绑定(出现的名字在一个函数的身体,而不是在其def或同等学历lambda)是什么,一个错误,因为我在长度在过去长,所以答案图所示:你早期绑定明确时,这就是你想要的东西,使用后期绑定默认时是你想要的,而这正是鉴于Python的设计的其余部分的背景下,正确的设计选择。
亚历克斯·马特利

1
@Alex Martelli:是的,很抱歉。我只是不习惯于后期绑定,可能是因为我认为在定义函数时实际上是在定义好东西,而意外的意外只会使我头疼。(不过,当我尝试使用Javascript进行功能处理而不是使用Python进行处理时,更多。)我知道许多人对后期绑定满意,并且它与Python的其余设计一致。不过,我仍然想阅读您的其他冗长的答案-链接?:-)
ShreevatsaR

3
亚历克斯是对的,这不是错误。但这是一个陷阱,吸引了许多lambda爱好者。有关haskel / function类型的参数的“ bug”部分,请参见Andrej Bauer的文章:math.andrej.com/2009/04/09/pythons-lambda-is-broken
ars

@ars:好的,谢谢您对Andrej Bauer帖子的链接。是的,后期绑定的影响肯定是我们数学类型(更糟的是,具有Haskell背景)的东西,总是发现非常出乎意料且令人震惊。:-)我不确定是否会像鲍尔教授所说的那样,将其称为设计错误,但是对于人类程序员来说,很难完全在一种思维方式和另一种思维方式之间切换。(或者也许这只是我不足的Python经验。)
ShreevatsaR

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>

1
不幸的是,部分函数无法为之腌制multiprocessing.Pool.map()stackoverflow.com/a/3637905/195139
到期

3
@wting该帖子来自2010。partial在python 2.7中是可腌制的。
弗雷德·福

22

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的答案中讨论的早期绑定和后期绑定的效果。


3
更重要的是,它partial是用C而不是纯Python编写的,这意味着它可以产生比仅创建一个调用另一个函数的函数更有效的可调用性。
chepner

12

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

默认情况下,由partial和lambda生成的函数都没有文档字符串(尽管您可以通过以下方式为任何对象设置文档字符串 __doc__)。

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


如果您已经测试了速度优势,那么可以期望部分速度比lambda有所提高?
Trilarion

1
当您说文档字符串是继承的时,您指的是哪个Python版本?在Python 2.7.15和Python 3.7.2中,它们不会被继承。这是一件好事,因为原始文档字符串不一定适用于带有部分应用参数的函数。
一月

对于python 2.7(docs.python.org/2/library/functools.html#partial-objects):“ 不会自动创建namedoc属性”。与3. [5-7]相同。
Yaroslav Nikitenko

您的链接中有一个错误:log_info = partial(log_template,level =“ info”)-这是不可能的,因为在该示例中,level不是关键字参数。python 2和3都说:“ TypeError:log_template()为参数'level'获得了多个值”。
Yaroslav Nikitenko '19

实际上,我手动创建了一个partial(f),它为doc字段提供了“ partial(func,* args,** keywords)-具有给定参数和关键字的部分应用\ n的新函数。\ n'(两者对于python 2和3)。
Yaroslav Nikitenko,

1

我在第三个示例中最快地了解了意图。

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

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


1
嗯,我实际上是相反的说服者,我花了更长的时间来解析该functools.partial调用,而lambda是不言而喻的。
David Z
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.