web-dev-qa-db-ja.com

Python:functools.partialが必要なのはなぜですか?

部分適用はクールです。どの機能が functools.partial ラムダを通過できないという申し出はありますか?

>>> 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はどういうわけかより効率的ですか、それとも読み取り可能ですか?

176
Nick Heiner

さて、ここに違いを示す例があります:

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のクロージャについて詳しく説明しています。

71
ars

Python(> = 2.7)の最新バージョンでは、picklepartialにできますが、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>
26
Fred Foo

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

on Python 3.3与えます:

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

つまり、パーシャルは作成にもう少し時間が必要ですが、実行にはかなり時間がかかります。これは、 ars の回答で説明されているアーリーバインディングとレイトバインディングの効果です。

21
Trilarion

アレックスが言及した追加の機能に加えて、functools.partialのもう1つの利点は速度です。 partialを使用すると、別のスタックフレームの構築(および破棄)を回避できます。

デフォルトでは、パーシャルでもラムダでも生成された関数にはdocstringがありません(ただし、__doc__)。

このブログで詳細を見つけることができます: Pythonの部分関数アプリケーション

11
Leonardo.Z

3番目の例で意図を最も早く理解します。

ラムダを解析するとき、標準ライブラリが直接提供するよりも複雑/奇妙なものを期待しています。

また、3番目の例がsum2の完全な署名に依存しない唯一の例であることに気付くでしょう。したがって、わずかに疎結合になります。

1
Jon-Eric