Si le but est d'avoir le même type d'effet dans votre code que #ifdef WINDOWS / #endif .. voici un moyen de le faire (je suis sur un mac btw).
Boîtier simple, sans chaînage
>>> 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)
Ainsi, avec cette implémentation, vous obtenez la même syntaxe que vous avez dans votre question.
>>> @macos
... def zulu():
... print("world")
...
>>> @windows
... def zulu():
... print("hello")
...
>>> zulu()
world
>>>
Ce que le code ci-dessus fait, essentiellement, est d'attribuer zulu à zulu si la plate-forme correspond. Si la plate-forme ne correspond pas, elle renverra zulu si elle a été précédemment définie. S'il n'a pas été défini, il renvoie une fonction d'espace réservé qui déclenche une exception.
Les décorateurs sont conceptuellement faciles à comprendre si vous gardez à l'esprit que
@mydecorator
def foo():
pass
est analogue à:
foo = mydecorator(foo)
Voici une implémentation utilisant un décorateur paramétré:
>>> 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")
Les décorateurs paramétrés sont analogues à foo = mydecorator(param)(foo)
.
J'ai un peu mis à jour la réponse. En réponse aux commentaires, j'ai étendu sa portée d'origine pour inclure l'application aux méthodes de classe et pour couvrir les fonctions définies dans d'autres modules. Dans cette dernière mise à jour, j'ai pu réduire considérablement la complexité de déterminer si une fonction a déjà été définie.
[Une petite mise à jour ici ... Je ne pouvais tout simplement pas mettre cela de côté - cela a été un exercice amusant] J'ai fait encore plus de tests et j'ai constaté que cela fonctionne généralement sur les callables - pas seulement sur les fonctions ordinaires; vous pouvez également décorer les déclarations de classe appelables ou non. Et il prend en charge les fonctions internes des fonctions, donc des choses comme cela sont possibles (bien que ce ne soit probablement pas un bon style - ce n'est qu'un code de test):
>>> @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.")
Ce qui précède montre le mécanisme de base des décorateurs, comment accéder à la portée de l'appelant et comment simplifier plusieurs décorateurs qui ont un comportement similaire en ayant une fonction interne contenant l'algorithme commun défini.
Support de chaînage
Pour prendre en charge le chaînage de ces décorateurs en indiquant si une fonction s'applique à plusieurs plates-formes, le décorateur pourrait être implémenté comme suit:
>>> 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)
De cette façon, vous soutenez le chaînage:
>>> @macos
... @linux
... def foo():
... print("works!")
...
>>> foo()
works!
my_callback = windows(<actual function definition>)
- donc le nommy_callback
sera écrasé, indépendamment de ce que le décorateur pourrait faire. La seule façon dont la version Linux de la fonction pourrait se retrouver dans cette variable est de lawindows()
renvoyer - mais la fonction n'a aucun moyen de connaître la version Linux. Je pense que la façon la plus typique d'y parvenir est d'avoir les définitions de fonctions spécifiques au système d'exploitation dans des fichiers séparés, et conditionnellementimport
une seule d'entre elles.