部分应用很酷。functools有什么功能。部分报价,你不能通过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在某种程度上更有效或更可读吗?


我最容易理解第三个例子的意图。

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

另外,你会注意到第三个例子是唯一一个不依赖于sum2的完整签名的例子;从而使它的耦合稍微松散一些。


好吧,这里有一个例子可以说明区别:

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

Python闭包(第2部分) Python闭包(第3部分)


functools有什么功能。部分报价,你不能通过lambdas?

Not much in terms of extra functionality (but, see later) – and, readability is in the eye of the beholder. Most people who are familiar with functional programming languages (those in the Lisp/Scheme families in particular) appear to like lambda just fine – I say "most", definitely not all, because Guido and I assuredly are among those "familiar with" (etc) yet think of lambda as an eyesore anomaly in Python... He was repentant of ever having accepted it into Python whereas planned to remove it from Python 3, as one of "Python's glitches". I fully supported him in that. (I love lambda in Scheme... while its limitations in Python, and the weird way it just doesn't fit in with the rest of the language, make my skin crawl).

然而,对lambda爱好者来说却不是这样——他们上演了Python历史上最接近反叛的事情之一,直到Guido反悔并决定留下lambda。 对functools的一些可能的添加(使函数返回常量、标识符等)并没有发生(以避免显式复制更多lambda的功能),尽管部分保留了(不是完全复制,也不碍眼)。

记住,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))

但我衷心希望,即使是最狂热的羔羊肉爱好者,也不会认为这种恐怖比部分呼叫更容易读懂!-)。“属性设置”部分甚至更难,因为Python的lambda的“主体是一个单一表达式”的限制(加上赋值永远不能成为Python表达式的一部分)……你最终会“在表达式中伪造赋值”,通过扩展列表理解能力远远超出其设计限制…:

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

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


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

由partial和lambdas生成的函数默认都没有文档字符串(尽管你可以通过__doc__为任何对象设置文档字符串)。

你可以在这个博客中找到更多细节:Python中的部分函数应用


在最新版本的Python(>=2.7)中,你可以pickle一个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>

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的回答中讨论的早期和晚期绑定的影响。


函数在计算某些变量时很有用。

从一个局外人的角度来看,这里有一系列更友好的例子:

from functools import partial

sum = lambda x, y: x + y            # sum(x, y) == x + y

n = 2
normalSum = lambda x: sum(x, n)     # normalSum(x) == sum(x, y=n)
partialSum = partial(sum, y = n)    # partialSum(sum(y=n)) == sum(x, 2)
print(normalSum(2), partialSum(2))  # 4 4

n = 6
print(normalSum(2), partialSum(2))  # 8 4

注意这个部分是如何保存当时n的值的。

...
n = 2
partialSumOrig = partial(sum, y = n)        # partialSumOrig(sum(y=n)) == sum(x, 2)
n = 6
partialSumNew = partial(sum, y = n)         # partialSumNew(sum(y=n)) == sum(x, 6)

print(partialSumOrig(2), partialSumNew(2))  # 4 8

演示如何将参数传递到嵌套lambdas的额外示例:

...
n = 8
partialSumOrig = partial(sum, y = n)  # partialSumOrig(sum(y=n)) == sum(x, 8)
partialSumNew = partial(sum, n)       # partialSumNew(sum(n)) == sum(8, y)

print(partialSumOrig(2))  # 10        # partialSumOrig(sum(2, 8)) == sum(2, 8)
print(partialSumNew(2))   # 10        # partialSumNew(sum(8, 2)) == sum(8, 2)

最后一个例子展示了参数如何以partial形式传递:

...
n = 2
m = 2
partialSumSilly = partial(sum, n, m)  # partialSumSilly(sum(n, m)) == sum(2, 2)
print(partialSumSilly())              # 4

最重要的是:

normalSum()的行为类似于后期绑定,其中n在运行时计算。 partialSum()的行为类似于早期绑定,其中n在定义时被求值。

注意:实际上,由于cpython的解释性,几乎所有东西都是后期绑定。