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?
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 lambda
in 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 ...! -)
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:
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>
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.
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
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.