Funktionsschluss vs. callable class

8

In vielen Fällen gibt es zwei Implementierungsoptionen: eine Closure- und eine Callable-Klasse. Zum Beispiel

%Vor%

oder

%Vor%

Welche Faktoren sollte man berücksichtigen, wenn man die Wahl in beide Richtungen trifft?

Ich denke an zwei:

  • Es scheint, eine Schließung hätte immer eine bessere Leistung (kann nicht denke an ein Gegenbeispiel).

  • Ich denke, es gibt Fälle, in denen eine Schließung die Aufgabe nicht erfüllen kann (z. B. wenn sein Zustand ändert sich im Laufe der Zeit).

Bin ich richtig in diesen? Was könnte noch hinzugefügt werden?

    
max 23.01.2012, 03:04
quelle

5 Antworten

9

Verschlüsse sind schneller. Klassen sind flexibler (d. H. Mehr Methoden verfügbar als nur __call __).

    
Raymond Hettinger 23.01.2012, 03:44
quelle
2

Ich weiß, dass dies ein älteres Posting ist, aber ein Faktor, den ich nicht aufgelistet sah, ist, dass Sie in Python (nicht-lokal) eine lokale Variable, die in der referenzierenden Umgebung enthalten ist, nicht ändern können. (In Ihrem Beispiel ist eine solche Änderung nicht wichtig, aber technisch gesehen bedeutet das Fehlen einer Änderung einer solchen Variable, dass es sich nicht um eine echte Schließung handelt.)

Der folgende Code funktioniert beispielsweise nicht:

%Vor%

Der Aufruf von c löst eine UnboundLocalError-Ausnahme aus.

Dies ist leicht zu umgehen, indem man ein veränderbares Element wie ein Wörterbuch verwendet:

%Vor%

Aber das ist natürlich nur ein Workaround.

    
Adam Donahue 27.12.2013 01:18
quelle
0

Ich halte den Klassenansatz auf einen Blick für leichter verständlich und daher wartbarer. Da dies eine der Prämissen von gutem Python-Code ist, denke ich, dass andere Dinge besser sind, als eine verschachtelte Funktion.

Das ist ein Teil, das ist einer der Fälle, in denen die flexible Natur von Python die Sprache gegen die "Es sollte eine, und vorzugsweise nur eine, offensichtliche Art und Weise, etwas zu tun" Prädikat für die Codierung in Python.

Der Leistungsunterschied für beide Seiten sollte vernachlässigbar sein - und wenn Sie Code haben, bei dem Leistung auf dieser Ebene von Bedeutung ist, sollten Sie ihn unbedingt profilieren und die relevanten Teile optimieren, wobei Sie möglicherweise einen Teil Ihres Codes als nativen Code umschreiben.

Aber ja, wenn es eine enge Schleife mit den Zustandsvariablen gab, sollte die Bewertung der Abschlussvariablen etwas schneller sein als die Bewertung der Klassenattribute. Natürlich würde dies durch einfaches Einfügen einer Zeile wie op = self.op in die Klassenmethode vor dem Eintritt in die Schleife überwunden werden, wodurch der Variablenzugriff innerhalb der Schleife auf eine lokale Variable erfolgen würde - dies würde eine Attributsuche vermeiden und Holen für jeden Zugriff. Auch hier sollten die Leistungsunterschiede vernachlässigbar sein, und Sie haben ein ernsthafteres Problem, wenn Sie diese wenig zusätzliche Leistung benötigen und in Python programmieren.

    
jsbueno 23.01.2012 03:19
quelle
0

Bitte beachten Sie, dass meine ursprüngliche Antwort aufgrund eines Fehlers, der zuvor in meinem Testcode gefunden wurde, falsch war. Die überarbeitete Version folgt.

Ich habe ein kleines Programm erstellt, um die Laufzeit und den Speicherverbrauch zu messen. Ich habe die folgende aufrufbare Klasse und eine Schließung erstellt:

%Vor%

Ich habe Aufrufe an einfache Funktionen zeitlich abgestimmt, die eine unterschiedliche Anzahl von Argumenten akzeptieren ( math.sqrt() mit 1 Argument, math.pow() mit 2 und max() mit 12).

Ich habe CPython 2.7.10 und 3.4.3+ auf Linux x64 verwendet. Ich konnte nur Memory-Profiling auf Python 2 durchführen. Der Quellcode, den ich verwendet habe, ist hier verfügbar.

Meine Schlussfolgerungen sind:

  • Closures laufen schneller als äquivalente, aufrufbare Klassen: etwa 3 mal schneller bei Python 2, aber nur 1,5 mal schneller bei Python 3. Die Einschränkung besteht sowohl darin, dass Closing langsamer und aufrufbare Klassen langsamer wird.
  • Closures benötigen weniger Speicher als gleichwertige, aufrufbare Klassen: ungefähr 2/3 des Speichers (nur auf Python 2 getestet).
  • Obwohl es nicht Teil der ursprünglichen Frage ist, ist es interessant festzustellen, dass der Overhead für die Laufzeit von Anrufen, die über eine Schließung ausgeführt werden, in etwa dem eines Aufrufs von math.pow() entspricht, während er über eine aufrufbare Klasse ungefähr doppelt so hoch ist / li>

Dies sind sehr grobe Schätzungen, und sie können mit der Hardware, dem Betriebssystem und der Funktion, die Sie auch vergleichen, variieren. Es gibt Ihnen jedoch eine Vorstellung über die Auswirkungen der Verwendung jeder Art von Callable.

Daher unterstützt dies (im Gegensatz zu dem, was ich vorher geschrieben habe), dass die akzeptierte Antwort von @RaymondHettinger korrekt ist und dass Closures für indirekte Aufrufe bevorzugt werden sollten, zumindest solange es die Lesbarkeit nicht beeinträchtigt . Auch dank @AXO für den Hinweis auf den Fehler in meinem ursprünglichen Code.

    
Yuval 14.01.2017 14:47
quelle
-1

Ich würde class Beispiel mit etwas wie:

neu schreiben %Vor%

Das ergibt 0.40 usec / pass (Funktion 0.31, also 29% langsamer) auf meinem Rechner mit Python 3.2.2. Ohne Verwendung von object als Basisklasse ergibt sich 0,65 usec / pass (d. H. 55% langsamer als object basierend). Und aus irgendeinem Grund gibt der Code mit der Überprüfung von op in __call__ fast die gleichen Ergebnisse, als wäre es in __init__ gemacht worden. Mit object als Basis und Check innerhalb __call__ ergibt 0.61 usec / pass.

Der Grund, warum Sie Klassen verwenden, könnte Polymorphismus sein.

%Vor%     
ony 23.01.2012 04:13
quelle