Ich würde gerne wissen, ob es möglich ist, die Python-Funktionsdefinition basierend auf globalen Einstellungen (z. B. Betriebssystem) zu steuern. Beispiel:
@linux
def my_callback(*args, **kwargs):
print("Doing something @ Linux")
return
@windows
def my_callback(*args, **kwargs):
print("Doing something @ Windows")
return
Wenn dann jemand Linux verwendet, wird die erste Definition von my_callback
verwendet und die zweite wird stillschweigend ignoriert.
Es geht nicht darum, das Betriebssystem zu bestimmen, sondern um Funktionsdefinitionen / Dekoratoren.
my_callback = windows(<actual function definition>)
- so der Namemy_callback
wird überschrieben, unabhängig davon , was der Dekorateur tun könnte. Die Linux-Version der Funktion kann nur dann in diese Variable gelangen, wenn siewindows()
zurückgegeben wird. Die Funktion kann jedoch nichts über die Linux-Version wissen. Ich denke, der typischere Weg, dies zu erreichen, besteht darin, die betriebssystemspezifischen Funktionsdefinitionen in separaten Dateien zu haben, und bedingtimport
nur in einer von ihnen.functools.singledispatch
, die etwas Ähnliches wie das tut, was Sie wollen. Dortregister
kennt der Dekorateur den Dispatcher (weil er ein Attribut der Dispatch-Funktion ist und für diesen bestimmten Dispatcher spezifisch ist), sodass er den Dispatcher zurückgeben und die Probleme mit Ihrem Ansatz vermeiden kann.uuid.getnode()
. (Das heißt, Todd's Antwort hier ist ziemlich gut.)Antworten:
Wenn das Ziel darin besteht, den gleichen Effekt in Ihrem Code zu erzielen wie #ifdef WINDOWS / #endif .. Hier ist eine Möglichkeit, dies zu tun (ich bin übrigens auf einem Mac).
Einfacher Fall, keine Verkettung
Mit dieser Implementierung erhalten Sie also dieselbe Syntax, die Sie in Ihrer Frage haben.
Der obige Code weist Zulu im Wesentlichen Zulu zu, wenn die Plattform übereinstimmt. Wenn die Plattform nicht übereinstimmt, gibt sie zulu zurück, wenn es zuvor definiert wurde. Wenn es nicht definiert wurde, wird eine Platzhalterfunktion zurückgegeben, die eine Ausnahme auslöst.
Dekorateure sind konzeptionell leicht herauszufinden, wenn Sie dies berücksichtigen
ist analog zu:
Hier ist eine Implementierung mit einem parametrisierten Dekorator:
Parametrierte Dekorateure sind analog zu
foo = mydecorator(param)(foo)
.Ich habe die Antwort ziemlich aktualisiert. Als Reaktion auf Kommentare habe ich den ursprünglichen Bereich um die Anwendung auf Klassenmethoden und auf Funktionen erweitert, die in anderen Modulen definiert sind. In diesem letzten Update konnte ich die Komplexität bei der Feststellung, ob eine Funktion bereits definiert wurde, erheblich reduzieren.
[Ein kleines Update hier ... Ich konnte das einfach nicht ablegen - es war eine lustige Übung] Ich habe dies noch einmal getestet und festgestellt, dass es im Allgemeinen bei Callables funktioniert - nicht nur bei normalen Funktionen. Sie können auch Klassendeklarationen dekorieren, ob aufrufbar oder nicht. Und es unterstützt innere Funktionen von Funktionen, so dass solche Dinge möglich sind (obwohl wahrscheinlich kein guter Stil - dies ist nur Testcode):
Das Obige zeigt den grundlegenden Mechanismus von Dekoratoren, wie auf den Bereich des Anrufers zugegriffen werden kann und wie mehrere Dekoratoren mit ähnlichem Verhalten vereinfacht werden können, indem eine interne Funktion mit dem definierten gemeinsamen Algorithmus definiert wird.
Verkettungsunterstützung
Um die Verkettung dieser Dekoratoren zu unterstützen und anzugeben, ob eine Funktion für mehr als eine Plattform gilt, könnte der Dekorator folgendermaßen implementiert werden:
Auf diese Weise unterstützen Sie die Verkettung:
quelle
macos
undwindows
im selben Modul wie definiert sindzulu
. Ich glaube, dies wird auch dazu führen, dass die Funktion so belassen wird, alsNone
ob die Funktion nicht für die aktuelle Plattform definiert wäre, was zu einigen sehr verwirrenden Laufzeitfehlern führen würde.Während die
@decorator
Syntax gut aussieht, erhalten Sie mit einem einfachen genau das gleiche Verhalten wie gewünschtif
.Bei Bedarf kann auf diese Weise auch leicht erzwungen werden, dass ein Fall übereinstimmt.
quelle
def callback_windows(...)
unddef callback_linux(...)
dannif windows: callback = callback_windows
usw. Aber so oder so ist dies viel einfacher zu lesen, zu debuggen und zu warten.elif
, da es niemals der erwartete Fall sein wird, dass mehr als eines vonlinux
/windows
/macOS
wahr sein wird. In der Tat würde ich wahrscheinlich nur eine einzelne Variable definierenp = platform.system()
und dannif p == "Linux"
usw. anstelle mehrerer boolescher Flags verwenden. Nicht vorhandene Variablen können nicht nicht synchronisiert werden.elif
sicherlich hat seine Vorteile - speziell ein nachlaufelse
+ ,raise
um sicherzustellen , dass zumindest in einem Fall tat Spiel. Für die Bewertung des Prädikats bevorzuge ich eine Vorbewertung - dies vermeidet Doppelarbeit und entkoppelt die Definition und Verwendung. Auch wenn das Ergebnis nicht in Variablen gespeichert ist, gibt es jetzt fest codierte Werte, die trotzdem nicht mehr synchron sind. Ich kann mich nie an die verschiedenen magischen Saiten für die verschiedenen Mittel erinnern, zBplatform.system() == "Windows"
gegensys.platform == "win32"
...Enum
oder nur um eine Reihe von Konstanten handelt.Nachfolgend finden Sie eine mögliche Implementierung für diesen Mechaniker. Wie in den Kommentaren erwähnt, kann es vorzuziehen sein, eine "Master-Dispatcher" -Schnittstelle zu implementieren, wie die in
functools.singledispatch
, um den Status zu verfolgen, der mit den mehreren überladenen Definitionen verbunden ist. Ich hoffe, dass diese Implementierung zumindest einen Einblick in die Probleme bietet, mit denen Sie möglicherweise bei der Entwicklung dieser Funktionalität für eine größere Codebasis zu kämpfen haben.Ich habe nur getestet, ob die unten stehende Implementierung wie auf Linux-Systemen angegeben funktioniert, daher kann ich nicht garantieren, dass diese Lösung die Erstellung plattformspezifischer Funktionen angemessen ermöglicht. Bitte verwenden Sie diesen Code nicht in einer Produktionsumgebung, ohne ihn vorher gründlich zu testen.
Um diesen Dekorator verwenden zu können, müssen wir zwei Indirektionsebenen durcharbeiten. Zunächst müssen wir angeben, auf welche Plattform der Dekorateur reagieren soll. Dies wird durch die Linie
implement_linux = implement_for_os('Linux')
und das obige Gegenstück zum Fenster erreicht. Als nächstes müssen wir die vorhandene Definition der überladenen Funktion weitergeben. Dieser Schritt muss an der Definitionsstelle ausgeführt werden, wie unten gezeigt.Um eine plattformspezifische Funktion zu definieren, können Sie jetzt Folgendes schreiben:
Anrufe an
some_function()
werden entsprechend an die angegebene plattformspezifische Definition weitergeleitet.Persönlich würde ich nicht empfehlen, diese Technik im Produktionscode zu verwenden. Meiner Meinung nach ist es besser, das plattformabhängige Verhalten an jedem Ort, an dem diese Unterschiede auftreten, explizit zu beschreiben.
quelle
implement_for_os
gibt keinen Dekorator selbst zurück, sondern eine Funktion, die den Dekorator erzeugt, sobald die vorherige Definition der betreffenden Funktion vorliegt .Ich habe meinen Code geschrieben, bevor ich andere Antworten gelesen habe. Nachdem ich meinen Code fertiggestellt hatte, fand ich, dass der Code von @ Todd die beste Antwort ist. Wie auch immer, ich poste meine Antwort, weil ich Spaß hatte, als ich dieses Problem löste. Dank dieser guten Frage habe ich neue Dinge gelernt. Der Nachteil meines Codes besteht darin, dass bei jedem Aufruf von Funktionen ein Overhead zum Abrufen von Wörterbüchern vorhanden ist.
quelle
Eine saubere Lösung wäre, eine dedizierte Funktionsregistrierung zu erstellen, die versendet
sys.platform
. Dies ist sehr ähnlich zufunctools.singledispatch
. Der Quellcode dieser Funktion bietet einen guten Ausgangspunkt für die Implementierung einer benutzerdefinierten Version:Jetzt kann es ähnlich verwendet werden wie
singledispatch
:Die Registrierung funktioniert auch direkt mit den Funktionsnamen:
quelle