Jeśli celem jest uzyskanie takiego samego efektu w kodzie, jaki ma #ifdef WINDOWS / #endif .. oto sposób na zrobienie tego (jestem na komputerze Mac).
Prosta obudowa, bez łączenia
>>> 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)
Dzięki tej implementacji otrzymujesz taką samą składnię, jaką masz w swoim pytaniu.
>>> @macos
... def zulu():
... print("world")
...
>>> @windows
... def zulu():
... print("hello")
...
>>> zulu()
world
>>>
Zasadniczo to, co robi powyższy kod, polega na przypisaniu zulu do zulu, jeśli platforma pasuje. Jeśli platforma nie pasuje, zwróci Zulu, jeśli została wcześniej zdefiniowana. Jeśli nie został zdefiniowany, zwraca funkcję zastępczą, która wywołuje wyjątek.
Dekoratorzy są koncepcyjnie łatwi do zrozumienia, jeśli weźmiesz to pod uwagę
@mydecorator
def foo():
pass
jest analogiczny do:
foo = mydecorator(foo)
Oto implementacja wykorzystująca sparametryzowany 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")
Sparametryzowane dekoratory są analogiczne do foo = mydecorator(param)(foo)
.
Zaktualizowałem odpowiedź całkiem sporo. W odpowiedzi na komentarze rozszerzyłem swój pierwotny zakres o aplikacje do metod klasowych oraz o funkcje zdefiniowane w innych modułach. W tej ostatniej aktualizacji byłem w stanie znacznie zmniejszyć złożoność związaną z określeniem, czy funkcja została już zdefiniowana.
[Mała aktualizacja tutaj ... Po prostu nie mogłem tego odłożyć - to było zabawne ćwiczenie] Przeprowadziłem kilka testów tego i odkryłem, że działa ogólnie na wywołaniach - nie tylko zwykłych funkcjach; możesz również ozdobić deklaracje klasowe, czy to na żądanie, czy nie. I obsługuje wewnętrzne funkcje funkcji, więc takie rzeczy są możliwe (chociaż prawdopodobnie nie w dobrym stylu - to tylko kod testowy):
>>> @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.")
Powyżej pokazuje podstawowy mechanizm dekoratorów, jak uzyskać dostęp do zakresu dzwoniącego i jak uprościć wiele dekoratorów, które zachowują się podobnie, poprzez zdefiniowanie wewnętrznej funkcji zawierającej wspólny algorytm.
Łańcuchowe wsparcie
Aby wesprzeć tworzenie łańcuchów tych dekoratorów wskazujących, czy funkcja dotyczy więcej niż jednej platformy, dekorator można zaimplementować w następujący sposób:
>>> 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)
W ten sposób wspierasz tworzenie łańcuchów:
>>> @macos
... @linux
... def foo():
... print("works!")
...
>>> foo()
works!
my_callback = windows(<actual function definition>)
- więc nazwamy_callback
zostanie zastąpiona, niezależnie od tego, co może zrobić dekorator. Jedynym sposobem, w jaki wersja Linux funkcji może znaleźć się w tej zmiennej, jestwindows()
jej zwrócenie - ale funkcja nie ma możliwości dowiedzenia się o wersji Linux. Myślę, że bardziej typowym sposobem osiągnięcia tego jest posiadanie definicji funkcji specyficznych dla systemu operacyjnego w osobnych plikach, a warunkowoimport
tylko jeden z nich.