Python:为什么需要 functools.partial?

发布于 2024-09-10 14:10:59 字数 474 浏览 2 评论 0原文

部分应用很酷。 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 是否更高效或更具可读性?

Partial application is cool. What functionality does functools.partial offer that you can't get through 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

Is functools somehow more efficient, or readable?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

坚持沉默 2024-09-17 14:10:59

functools.partial 提供了哪些功能是您无法通过 lambda 实现的?

没有太多额外的功能(但是,见下文)——而且,可读性是情人眼里出西施。
大多数熟悉函数式编程语言的人(特别是 Lisp/Scheme 系列中的语言)似乎都非常喜欢 lambda - 我说的是“大多数”,绝对不是全部,因为 Guido 和我确实是那些“熟悉”(等等)的人之一,但仍认为 lambda 是 Python 中令人讨厌的异常现象......
他对将其接受到 Python 中感到后悔,同时计划将其从 Python 3 中删除,作为“Python 的故障”之一。
我完全支持他。 (我喜欢Scheme中的lambda...而它在Python中的局限性,以及它不适合的奇怪方式 加上其余的语言,让我起鸡皮疙瘩)。

然而,对于成群的 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))

但我亲爱的希望即使是最热心的 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]

现在将命名参数可重写性以及三个属性的设置结合到一个表达式中,并告诉我可读性如何 那将是......!

What functionality does functools.partial offer that you can't get through 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).

Not so, however, for the hordes of lambda lovers -- who staged one of the closest things to a rebellion ever seen in Python's history, until Guido backtracked and decided to leave lambda in.
Several possible additions to functools (to make functions returning constants, identity, etc) didn't happen (to avoid explicitly duplicating more of lambda's functionality), though partial did of course remain (it's no total duplication, nor is it an eyesore).

Remember that lambda's body is limited to be an expression, so it's got limitations. For example...:

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

functools.partial's returned function is decorated with attributes useful for introspection -- the function it's wrapping, and what positional and named arguments it fixes therein. Further, the named arguments can be overridden right back (the "fixing" is rather, in a sense, the setting of defaults):

>>> f('23', base=10)
23

So, as you see, it's definely not as simplistic as lambda s: int(s, base=2)!-)

Yes, you could contort your lambda to give you some of this – e.g., for the keyword-overriding,

>>> f = lambda s, **k: int(s, **dict({'base': 2}, **k))

but I dearly hope that even the most ardent lambda-lover doesn't consider this horror more readable than the partial call!-). The "attribute setting" part is even harder, because of the "body's a single expression" limitation of Python's lambda (plus the fact that assignment can never be part of a Python expression)... you end up "faking assignments within an expression" by stretching list comprehension well beyond its design limits...:

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

Now combine the named-arguments overridability, plus the setting of three attributes, into a single expression, and tell me just how readable that is going to be...!

坐在坟头思考人生 2024-09-17 14:10:59

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

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 限制”和闭包:

Well, here's an example that shows a difference:

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

These posts by Ivan Moore expand on the "limitations of lambda" and closures in python:

夜唯美灬不弃 2024-09-17 14:10:59

在最新版本的 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>

In the latest versions of Python (>=2.7), you can pickle a partial, but not a 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>
魄砕の薆 2024-09-17 14:10:59

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 的答案中进行了讨论。

Is functools somehow more efficient..?

As a partly answer to this I decided to test the performance. Here is my example:

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))

on Python 3.3 it gives:

lambda creation time 3.1743163756961392
lambda execution time 3.040552701787919
partial creation time 3.514482823352731
partial execution time 1.7113973411608114

Which means that partial needs a bit more time for creation but considerably less time for execution. This can well be the effect of the early and late binding which are discussed in the answer from ars.

指尖微凉心微凉 2024-09-17 14:10:59

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

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

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

Besides the extra functionality Alex mentioned, another advantage of functools.partial is speed. With partial you can avoid constructing (and destructing) another stack frame.

Neither the function generated by partial nor lambdas have docstrings by default (though you can set the doc string for any objects via __doc__).

You can find more details in this blog: Partial Function Application in Python

人事已非 2024-09-17 14:10:59

超级老的问题,但我想我会把它放在这里,以防它对某人有用。

与 lambda 相比,partial 的优点之一在于它们在循环中的行为方式。由于 lambda 计算参数的方式,在使用它们时很容易出错,那就是将 lambda 函数声明为循环的一部分(例如菜单项的回调方法)。

例如,这给出了一个答案,不熟悉 lambda 中这种行为的开发人员可能会感到惊讶,

funcs = []
for i in range(10):
    f = lambda j: i + j
    funcs.append(f)
print(f(1) for f in funcs)
# [10, 10, 10, 10, 10, 10, 10, 10, 10, 10]

但是,如果您使用部分,则不会遇到此问题;

parts = []
for i in range(10):
    p = partial(sum, (i, ))
    parts.append(p)
print([p(1) for p in parts])
# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

当然,有 lambda 解决这个问题的方法,但对于部分语法是(IMO )更加简单并且行为如您所期望的那样。

Super old question, but I thought I'd drop this here in case it's useful to someone.

One advantage of partials over lambda is in how they behave in loops. Due to the way lambda evaluate parameters an easy way to get tripped up in using them is when declaring lambda functions as part of a loop (e.g. callback methods for menu items).

For example, this gives you an answer that developers unfamiliar with this behaviour in lambdas might be surprised by,

funcs = []
for i in range(10):
    f = lambda j: i + j
    funcs.append(f)
print(f(1) for f in funcs)
# [10, 10, 10, 10, 10, 10, 10, 10, 10, 10]

However, if you use partials, you don't have this problem;

parts = []
for i in range(10):
    p = partial(sum, (i, ))
    parts.append(p)
print([p(1) for p in parts])
# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Of course, there are ways around the issue with lambda, but with partials the syntax is (IMO) far more straightforward and behaves as you'd expect.

凉墨 2024-09-17 14:10:59

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

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

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

I understand the intent quickest in the third example.

When I parse lambdas, I'm expecting more complexity/oddity than offered by the standard library directly.

Also, you'll notice that the third example is the only one which doesn't depend on the full signature of sum2; thus making it slightly more loosely coupled.

撩人痒 2024-09-17 14:10:59

泛函在评估某些变量时具有有用的用途。

来自局外人,这里有一系列更友好的示例:

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

展示如何将参数传递到嵌套 lambda 中的额外示例:

...
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)

展示如何在部分中传递参数的最后一个示例:

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

最大的收获是:

  • normalSum() 行为类似于后期绑定,其中 n 在运行时进行评估。
  • partialSum() 行为类似于早期绑定,其中 n 在定义时进行计算。

注意:实际上,由于其解释性质,几乎所有内容都是 cpython 中的后期绑定。

Functionals serve a useful purpose for when certain variables are evaluated.

Coming from an outsider, here's a series of more friendly examples:

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

Notice how the partial holds the value of whatever was n at the time.

...
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

Extra example showing how arguments are passed into nested 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)

One last example showing how arguments are passed in partials:

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

The big takeaway is that:

  • normalSum() behaves like a late binding, where n is evaluated when ran.
  • partialSum() behaves like an early binding, where n is evaluated when defined.

Note: In reality nearly everything is a late binding in cpython due to its interpreted nature.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文