Je voudrais savoir s'il est possible de contrôler la définition de la fonction Python en fonction des paramètres globaux (par exemple OS). Exemple:
@linux
def my_callback(*args, **kwargs):
print("Doing something @ Linux")
return
@windows
def my_callback(*args, **kwargs):
print("Doing something @ Windows")
return
Ensuite, si quelqu'un utilise Linux, la première définition de my_callback
sera utilisée et la seconde sera silencieusement ignorée.
Il ne s'agit pas de déterminer le système d'exploitation, mais de définir des fonctions / décorateurs.
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.functools.singledispatch
, qui fait quelque chose de similaire à ce que vous voulez. Là, leregister
décorateur connaît le répartiteur (car c'est un attribut de la fonction de répartition, et spécifique à ce répartiteur particulier), il peut donc retourner le répartiteur et éviter les problèmes avec votre approche.uuid.getnode()
,. (Cela dit, la réponse de Todd ici est assez bonne.)Réponses:
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
Ainsi, avec cette implémentation, vous obtenez la même syntaxe que vous avez dans votre question.
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
est analogue à:
Voici une implémentation utilisant un décorateur paramétré:
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):
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:
De cette façon, vous soutenez le chaînage:
la source
macos
etwindows
sont définis dans le même module quezulu
. Je crois que cela entraînera également que la fonction soit laissée commeNone
si la fonction n'était pas définie pour la plate-forme actuelle, ce qui entraînerait des erreurs d'exécution très déroutantes.Bien que la
@decorator
syntaxe soit agréable, vous obtenez exactement le même comportement que vous le souhaitez avec un simpleif
.Si nécessaire, cela permet également de faire en sorte que certains cas correspondent.
la source
def callback_windows(...)
etdef callback_linux(...)
puisif windows: callback = callback_windows
, etc. Mais dans les deux cas, c'est beaucoup plus facile à lire, à déboguer et à maintenir.elif
, car ce ne sera jamais le cas attendu que plus d'unlinux
/windows
/macOS
soit vrai. En fait, je définirais probablement une seule variablep = platform.system()
, puis utiliseraisif p == "Linux"
, etc. plutôt que plusieurs drapeaux booléens. Les variables qui n'existent pas ne peuvent pas être désynchronisées.elif
a certainement ses avantages - en particulier, une fuiteelse
+raise
pour veiller à ce qu'au moins un cas a fait partie. Quant à l'évaluation du prédicat, je préfère les pré-évaluer - cela évite la duplication et découples la définition et l'utilisation. Même si le résultat n'est pas stocké dans des variables, il existe maintenant des valeurs codées en dur qui peuvent se désynchroniser tout de même. Je ne me souviens jamais des différentes cordes magiques pour les différents moyens, par exempleplatform.system() == "Windows"
contresys.platform == "win32"
, ...Enum
ou simplement un ensemble de constantes.Voici une implémentation possible pour ce mécanicien. Comme indiqué dans les commentaires, il peut être préférable de mettre en œuvre une interface "maître répartiteur", comme celle vue dans
functools.singledispatch
, pour garder une trace de l'état associé aux multiples définitions surchargées. J'espère que cette implémentation offrira au moins un aperçu des problèmes que vous devrez peut-être traiter lors du développement de cette fonctionnalité pour une base de code plus grande.J'ai seulement testé que l'implémentation ci-dessous fonctionne comme spécifié sur les systèmes Linux, donc je ne peux pas garantir que cette solution permet de manière adéquate la création de fonctions spécialisées de plate-forme. Veuillez ne pas utiliser ce code dans un environnement de production sans l'avoir soigneusement testé vous-même au préalable.
Pour utiliser ce décorateur, nous devons travailler sur deux niveaux d'indirection. Tout d'abord, nous devons spécifier à quelle plateforme nous voulons que le décorateur réponde. Ceci est accompli par la ligne
implement_linux = implement_for_os('Linux')
et son homologue de Windows ci-dessus. Ensuite, nous devons transmettre la définition existante de la fonction surchargée. Cette étape doit être effectuée sur le site de définition, comme illustré ci-dessous.Pour définir une fonction spécialisée de plate-forme, vous pouvez maintenant écrire ce qui suit:
Appels à
some_function()
seront correctement acheminés vers la définition spécifique à la plate-forme fournie.Personnellement, je ne conseillerais pas d'utiliser cette technique dans le code de production. À mon avis, il vaut mieux être explicite sur le comportement dépendant de la plate-forme à chaque endroit où ces différences se produisent.
la source
implement_for_os
ne renvoie pas un décorateur lui-même, mais renvoie plutôt une fonction qui produira le décorateur une fois fourni avec la définition précédente de la fonction en question.J'ai écrit mon code avant de lire d'autres réponses. Après avoir terminé mon code, j'ai trouvé que le code de @ Todd était la meilleure réponse. Quoi qu'il en soit, je poste ma réponse parce que je me suis senti amusant pendant que je résolvais ce problème. J'ai appris de nouvelles choses grâce à cette bonne question. L'inconvénient de mon code est qu'il existe une surcharge pour récupérer les dictionnaires à chaque appel de fonctions.
la source
Une solution propre consisterait à créer un registre de fonctions dédié qui serait distribué
sys.platform
. C'est très similaire àfunctools.singledispatch
. Le code source de cette fonction fournit un bon point de départ pour implémenter une version personnalisée:Maintenant, il peut être utilisé comme
singledispatch
:L'enregistrement fonctionne également directement sur les noms des fonctions:
la source