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
>>> def _ifdef_decorator_impl(plat, func, frame):
... if platform.system() == plat:
... return func
... elif func.__name__ in frame.f_locals:
... return frame.f_locals[func.__name__]
... else:
... def _not_implemented(*args, **kwargs):
... raise NotImplementedError(
... f"Function {func.__name__} is not defined "
... f"for platform {platform.system()}.")
... return _not_implemented
...
...
>>> def windows(func):
... return _ifdef_decorator_impl('Windows', func, sys._getframe().f_back)
...
>>> def macos(func):
... return _ifdef_decorator_impl('Darwin', func, sys._getframe().f_back)
Mit dieser Implementierung erhalten Sie also dieselbe Syntax, die Sie in Ihrer Frage haben.
>>> @macos
... def zulu():
... print("world")
...
>>> @windows
... def zulu():
... print("hello")
...
>>> zulu()
world
>>>
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
@mydecorator
def foo():
pass
ist analog zu:
foo = mydecorator(foo)
Hier ist eine Implementierung mit einem parametrisierten Dekorator:
>>> def ifdef(plat):
... frame = sys._getframe().f_back
... def _ifdef(func):
... return _ifdef_decorator_impl(plat, func, frame)
... return _ifdef
...
>>> @ifdef('Darwin')
... def ice9():
... print("nonsense")
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):
>>> @macos
... class CallableClass:
...
... @macos
... def __call__(self):
... print("CallableClass.__call__() invoked.")
...
... @macos
... def func_with_inner(self):
... print("Defining inner function.")
...
... @macos
... def inner():
... print("Inner function defined for Darwin called.")
...
... @windows
... def inner():
... print("Inner function for Windows called.")
...
... inner()
...
... @macos
... class InnerClass:
...
... @macos
... def inner_class_function(self):
... print("Called inner_class_function() Mac.")
...
... @windows
... def inner_class_function(self):
... print("Called inner_class_function() for windows.")
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:
>>> class IfDefDecoratorPlaceholder:
... def __init__(self, func):
... self.__name__ = func.__name__
... self._func = func
...
... def __call__(self, *args, **kwargs):
... raise NotImplementedError(
... f"Function {self._func.__name__} is not defined for "
... f"platform {platform.system()}.")
...
>>> def _ifdef_decorator_impl(plat, func, frame):
... if platform.system() == plat:
... if type(func) == IfDefDecoratorPlaceholder:
... func = func._func
... frame.f_locals[func.__name__] = func
... return func
... elif func.__name__ in frame.f_locals:
... return frame.f_locals[func.__name__]
... elif type(func) == IfDefDecoratorPlaceholder:
... return func
... else:
... return IfDefDecoratorPlaceholder(func)
...
>>> def linux(func):
... return _ifdef_decorator_impl('Linux', func, sys._getframe().f_back)
Auf diese Weise unterstützen Sie die Verkettung:
>>> @macos
... @linux
... def foo():
... print("works!")
...
>>> foo()
works!
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.