2015-08-08 5 views
7

Betrachten Sie das folgende Python-Schnipsel über Zusammensetzung Funktionen:Was ist die Logik hinter dieser speziellen Python-Funktionszusammensetzung?

from functools import reduce 
def compose(*funcs): 
    # compose a group of functions into a single composite (f(g(h(..(x)..))) 
    return reduce(lambda f, g: lambda *args, **kwargs: f(g(*args, **kwargs)), funcs) 


### --- usage example: 
from math import sin, cos, sqrt 
mycompositefunc = compose(sin,cos,sqrt) 
mycompositefunc(2) 

Ich habe zwei Fragen:

  1. Kann jemand bitte erklären Sie mir die compose "operational Logik"? (Wie es funktioniert?)
  2. Wäre es möglich (und wie?), Das gleiche zu erhalten without using reduce dafür?

Ich sah schon here, here und here too, mein Problem NICHT ist zu verstehen, was lambda Mittel oder reduce hat (ich glaube, ich habe zum Beispiel, dass 2 im Verwendungsbeispiel etwas das erste Element sein wird, in funcs zusammengesetzt werden). Was finde ich schwerer zu verstehen ist vielmehr die Komplexität, wie die beiden lambda s wurde kombiniert/verschachtelten und mit *args, **kwargs hier als reduce erste Argument ...


EDIT:

Zunächst all, @Martijn und @Borealid, danke für deine Mühe und deine Antworten und für die Zeit, die du mir widmest. (Sorry für die Verspätung, ich tue dies in meiner Freizeit und nicht immer sehr viel haben ...)

Ok, jetzt Fakten kommen ...

über ersten Punkt auf meiner Frage:

vor allem erkannte ich, was habe ich nicht wirklich habe (aber ich hoffe, ich habe jetzt) ​​über jene *args, **kwargs variadische Argumente vor, dass mindestens**kwargsnicht zwingend (ich sage gut, nicht wahr?) Dies hat mir beispielsweise verständlich gemacht, warum mycompositefunc(2) mit diesem nur ein (nicht Schlüsselwort) übergebenen Argument arbeitet.

Ich erkannte, dass das Beispiel funktionieren würde, auch diese *args, **args im inneren Lambda mit einem einfachen x zu ersetzen. Ich stelle mir das vor, weil in dem Beispiel alle 3 zusammengesetzten Funktionen (sin, cos, sqrt) einen (und nur einen) Parameter erwarten ... und natürlich ein einzelnes Ergebnis zurückgeben ... also, genauer gesagt, es funktioniert, weil die zuerst komponierte Funktion erwarten nur einen Parameter (die folgenden anderen werden natürlich bekommen nur ein Argument hier, das ist das Ergebnis der vorherigen komponierten Funktionen, so dass Sie KEINE Funktionen komponieren können, die mehr als ein Argument nach dem ersten erwarten ... ich weiß, es ist ein bisschen contort aber ich denke, Sie bekommen, was ich versuche, ...)

Jetzt kommt zu erklären, was bleibt die eigentliche unklare Angelegenheit für mich hier:

lambda f, g: lambda *args, **kwargs: f(g(*args, **kwargs)) 

Wie funktioniert diese Lambda Verschachtelung "Magie"?

Mit all den großen Respekt, den Sie verdienen, und ich trage dich, scheint es mir, wie Sie beide falsch sind das Endergebnis zu dem Schluss kommen sind: sqrt(sin(cos(*args, **kw))). Es kann eigentlich nicht sein, die Reihenfolge der Appliance der sqrt-Funktion ist eindeutig umgekehrt: Es ist nicht das letzte, das komponiert wird, sondern das erste.

Ich sage das, weil:

>>> mycompositefunc(2) 
0.15531241172

sein Ergebnis

>>> sin(cos(sqrt(2))) 
0.15531241172

gleich ist, während Sie einen Fehler mit

>>> sqrt(sin(cos(2))) 
[...] 
ValueError: math domain error 

(das ist wegen versuchen, eine negative SquareRoot erhalten Schwimmer)

#P.S. for completeness: 

>>> sqrt(cos(sin(2))) 
0.7837731062727799 

>>> cos(sin(sqrt(2))) 
0.5505562169613818 

So verstehe ich, dass die Funktionen Zusammensetzung von der letzten bis zur ersten (dh: komponieren (sin, cos, sqrt) => sin (cos (sqrt (x)))) aber die "Warum? "und wie geht die Lambda-Verschachtelung‚Magie‘arbeitet? noch ein wenig unklar bleibt für mich ... Hilfe/Vorschläge sehr geschätzt!

Am 2. Punkt (etwa Umschreiben compose ohne reduzieren)

@Martijn Pieters: erste compose (die "eingewickelt" one) arbeitet und liefert genau das gleiche Ergebnis

>>> mp_compfunc = mp_compose(sin,cos,sqrt) 
>>> mp_compfunc(2) 
0.15531241172

das ungeöffnete Version, instea d, leider loops bis RuntimeError: maximum recursion depth exceeded ...

@Borealid: dein foo/bar-Beispiel wird nicht mehr als zwei Funktionen für die Komposition bekommen, aber ich denke, es war nur für Erklärungen nicht für die Beantwortung des zweiten Punktes gedacht, oder?

Antwort

5

Die *args, **kw Syntax in der lambda Signatur und Aufrufsyntax sind die beste Möglichkeit, beliebige Argumente weiterzugeben. Sie akzeptieren eine beliebige Anzahl von Positions- und Schlüsselwortargumenten und leiten diese einfach an einen nächsten Aufruf weiter. Sie könnten das Ergebnis der äußeren lambda als schreiben:

def _anonymous_function_(*args, **kw): 
    result_of_g = g(*args, **kw) 
    return f(result_of_g) 
return _anonymous_function 

Die compose Funktion ohne reduce() wie folgt neu geschrieben werden:

def compose(*funcs): 
    wrap = lambda f, g: lambda *args, **kw: f(g(*args, **kw)) 
    result = funcs[0] 
    for func in funcs[1:]: 
     result = wrap(result, func) 
    return result 

Dies tut genau dasselbe wie der reduce() Anruf; nenne das Lambda für die Funktionskette.

die ersten beiden Funktionen in der Reihenfolge So sind sin und cos, und diese werden ersetzt durch:

lambda *args, **kw: sin(cos(*args, **kw)) 

Diese dann zu dem nächsten Ruf als f, mit sqrtg übergeben, so dass Sie erhalten:

lambda *args, **kw: (lambda *args, **kw: sin(cos(*args, **kw)))(sqrt(*args, **kw))) 

, die vereinfacht werden:

lambda *args, **kw: sin(cos(sqrt(*args, **kw))) 

weil f() ein Lambda ist, das seine Argumente an den verschachtelten sin(cos())-Aufruf übergibt.

Am Ende dann haben Sie eine Funktion erzeugt, die sqrt() nennt, deren Ergebnis zu cos() geführt wird, und der Ausgang davon wird dann an sin() weitergegeben. Mit der *args, **kw können Sie eine beliebige Anzahl von Argumenten oder Schlüsselwortargumenten übergeben, so dass die compose()-Funktion auf alles als aufrufbar angewendet werden kann, vorausgesetzt, dass alle außer der ersten Funktion natürlich nur ein Argument benötigt.

+1

Es wäre besser, 'lambda x: x' als Anfangswert für die' reduce' oder die Schleife in Ihrer Antwort zu verwenden: so ist 'compose()' die korrekte Zusammensetzung von Nullfunktionen - eine Funktion das tut nichts. – Lynn

+0

@Mauris: aber es würde nicht das Ergebnis der Funktion 'reduce()' wie in der Frage geschrieben widerspiegeln. –

+0

@Mauris: Martijn ist direkt auf der Tatsache, es würde nicht die ursprüngliche Funktion wie in der Frage geschrieben, nette Anregung sowieso +1! – danicotra