Se l'obiettivo è avere lo stesso tipo di effetto nel tuo codice che ha #ifdef WINDOWS / #endif .. ecco un modo per farlo (sono su un mac btw).
Caso semplice, senza concatenamento
>>> 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)
Quindi con questa implementazione ottieni la stessa sintassi che hai nella tua domanda.
>>> @macos
... def zulu():
... print("world")
...
>>> @windows
... def zulu():
... print("hello")
...
>>> zulu()
world
>>>
Ciò che sta facendo il codice sopra, in sostanza, è assegnare zulu a zulu se la piattaforma corrisponde. Se la piattaforma non corrisponde, restituirà zulu se è stata precedentemente definita. Se non è stato definito, restituisce una funzione segnaposto che genera un'eccezione.
I decoratori sono concettualmente facili da capire se lo ricordi
@mydecorator
def foo():
pass
è analogo a:
foo = mydecorator(foo)
Ecco un'implementazione che utilizza un decoratore con parametri:
>>> 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")
I decoratori con parametri sono analoghi a foo = mydecorator(param)(foo)
.
Ho aggiornato abbastanza la risposta. In risposta ai commenti, ho ampliato il suo ambito originale per includere l'applicazione ai metodi di classe e per coprire le funzioni definite in altri moduli. In questo ultimo aggiornamento, sono stato in grado di ridurre notevolmente la complessità necessaria per determinare se una funzione è già stata definita.
[Un piccolo aggiornamento qui ... Non riuscivo proprio a metterlo giù - è stato un esercizio divertente] Ho fatto qualche altro test su questo, e ho scoperto che funziona generalmente su callable - non solo funzioni ordinarie; puoi anche decorare le dichiarazioni di classe che possono essere richiamate o meno. E supporta le funzioni interne di funzioni, quindi cose come questa sono possibili (anche se probabilmente non è un buon stile - questo è solo un codice di prova):
>>> @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.")
Quanto sopra dimostra il meccanismo di base dei decoratori, come accedere all'ambito del chiamante e come semplificare più decoratori che hanno un comportamento simile avendo una funzione interna contenente l'algoritmo comune definito.
Supporto per il concatenamento
Per supportare il concatenamento di questi decoratori indicando se una funzione si applica a più di una piattaforma, il decoratore potrebbe essere implementato in questo modo:
>>> 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)
In questo modo supporti il concatenamento:
>>> @macos
... @linux
... def foo():
... print("works!")
...
>>> foo()
works!
my_callback = windows(<actual function definition>)
- quindi il nomemy_callback
verrà sovrascritto, indipendentemente da ciò che il decoratore potrebbe fare. L'unico modo in cui la versione Linux della funzione potrebbe finire in quella variabile è sewindows()
restituita, ma la funzione non ha modo di conoscere la versione Linux. Penso che il modo più tipico per ottenere questo risultato sia avere le definizioni delle funzioni specifiche del sistema operativo in file separati, e condizionatamenteimport
solo uno di essi.