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
是不是在效率或者可读性上更好呢?
8 个回答
在最新版本的Python(2.7及以上),你可以对一个partial
对象进行pickle
操作,但不能对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>
好吧,这里有一个例子,展示了它们之间的区别:
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写的帖子,深入探讨了Python中“lambda的局限性”和闭包的概念:
functools.partial
提供了什么功能是你通过lambda无法实现的呢?
其实在额外的功能上并没有太多不同(但后面会提到)——而且可读性是因人而异的。
大多数熟悉函数式编程语言的人(特别是那些使用Lisp/Scheme的)似乎都挺喜欢lambda
的——我说“大多数”,但绝对不是“所有”,因为Guido和我也算是熟悉这些语言的人,但我们觉得lambda
在Python中简直是个眼睛的负担...
他曾后悔接受lambda
进入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
所以,你看,这绝对不是像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]
现在把命名参数的可覆盖性和设置三个属性结合到一个表达式中,告诉我这会有多可读...!