wake-up-neo.com

Python: Warum ist functools.partial notwendig?

Teilweise Anwendung ist cool. Welche Funktionalität hat functools.partial Angebot, dass du nicht durch Lambdas kommst?

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

Ist functools irgendwie effizienter oder lesbar?

176
Nick Heiner

Welche Funktionalität bietet functools.partial, die Sie mit Lambdas nicht erreichen können?

Nicht viel in Bezug auf Extra Funktionalität ​​(aber siehe später) - und die Lesbarkeit liegt im Auge des Betrachters.
Die meisten Leute, die mit funktionalen Programmiersprachen vertraut sind (insbesondere diejenigen in den LISP/Scheme-Familien), scheinen lambda zu mögen - ich sage definitiv "am meisten" nicht Alles, weil Guido und ich sicherlich zu den "Vertrauten" (usw.) gehören, die sich lambda in Python noch als Schandfleck-Anomalie vorstellen ...
Er hat es bereut, es jemals in Python= akzeptiert zu haben, obwohl geplant war, es aus Python 3 zu entfernen, als eine von "Pythons Pannen".
Ich habe ihn dabei voll unterstützt. (Ich liebe lambdain Schema ... während seine Grenzen in Python und die seltsame Art, wie es passt einfach nicht hinein mit dem Rest der Sprache meine Haut kriechen lassen).

Nicht so für die Horden von lambda -Liebhabern, die eine der engsten Verbindungen zu einer Rebellion in der Geschichte von Python hergestellt haben, bis Guido sich zurückzog und beschloss, lambda zu lassen.
Mehrere mögliche Ergänzungen zu functools (um Funktionen zur Rückgabe von Konstanten, Identität usw. zu veranlassen) sind nicht aufgetreten (um das explizite Duplizieren weiterer Funktionen von lambda zu vermeiden), obwohl partial ist natürlich geblieben (es ist keine total Vervielfältigung, noch ist es ein Schandfleck).

Denken Sie daran, dass der Körper von lambda auf ein Ausdruck beschränkt ist, sodass er Einschränkungen aufweist. Beispielsweise...:

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

Die zurückgegebene Funktion von functools.partial Ist mit Attributen dekoriert, die für die Introspektion nützlich sind - die Funktion, die sie umschließt, und welche positionellen und benannten Argumente sie darin festlegt. Außerdem können die genannten Argumente direkt zurückgesetzt werden (das "Reparieren" ist eher in gewissem Sinne das Setzen von Standardwerten):

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

Also, wie Sie sehen, ist es definitiv nicht so simpel wie lambda s: int(s, base=2)! -)

Ja, Sie könnten Ihr Lambda verformen, um Ihnen etwas davon zu geben - z. B. für das Keyword-Overriding,

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

aber ich hoffe sehr , dass selbst der leidenschaftlichste lambda - Liebhaber dieses Horror nicht für lesbarer hält als den partial Anruf!-). Der Teil "Attributeinstellung" ist noch schwieriger, da Pythons lambda auf "body's a single expression" beschränkt ist (plus der Tatsache, dass die Zuweisung niemals Teil eines Python Ausdruck) ... dass Sie "Aufgaben innerhalb eines Ausdrucks vortäuschen", indem Sie das Listenverständnis weit über seine Entwurfsgrenzen hinaus ausdehnen ...:

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

Kombinieren Sie nun die Überschreibbarkeit von named-arguments und die Einstellung von drei Attributen in einem einzigen Ausdruck und sagen Sie mir, wie lesbar dass sein wird ...! -)

241
Alex Martelli

Nun, hier ist ein Beispiel, das einen Unterschied zeigt:

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

Diese Beiträge von Ivan Moore erweitern die "Einschränkungen von Lambda" und Schließungen in Python:

71
ars

In den neuesten Versionen von Python (> = 2.7) können Sie pickle ein partial, aber kein 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

Ist functools irgendwie effizienter ..?

Als teilweise Antwort darauf habe ich beschlossen, die Leistung zu testen. Hier ist mein Beispiel:

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

auf Python 3.3 gibt es:

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

Dies bedeutet, dass teilweise etwas mehr Zeit für die Erstellung, aber erheblich weniger Zeit für die Ausführung benötigt. Dies kann durchaus der Effekt der frühen und späten Bindung sein, die in der Antwort von ars diskutiert werden.

21
Trilarion

Ein weiterer Vorteil von functools.partial ist neben der erwähnten Zusatzfunktionalität Alex die Geschwindigkeit. Mit partial können Sie vermeiden, einen anderen Stack-Frame zu konstruieren (und zu zerstören).

Weder die von Partial noch Lambdas generierte Funktion hat standardmäßig Docstrings (obwohl Sie den Docstring für beliebige Objekte über __doc__ Festlegen können).

Weitere Details finden Sie in diesem Blog: Partial Function Application in Python

11
Leonardo.Z

Ich verstehe die Absicht am schnellsten im dritten Beispiel.

Wenn ich Lambdas parse, erwarte ich mehr Komplexität/Kuriosität als von der Standardbibliothek direkt angeboten.

Außerdem werden Sie feststellen, dass das dritte Beispiel das einzige ist, das nicht von der vollständigen Signatur von sum2 Abhängt. Dadurch wird es etwas lockerer gekoppelt.

1
Jon-Eric