74 votes

Comment appeler des fonctions Python de manière dynamique

J'ai ce code :

fields = ['name','email']

def clean_name():
    pass

def clean_email():
    pass

Comment puis-je appeler clean_name() y clean_email() dynamiquement ?

Par exemple :

for field in fields:
    clean_{field}()

J'ai utilisé les accolades parce que c'est ainsi que je le faisais en PHP, mais cela ne fonctionne évidemment pas.

Comment faire cela avec Python ?

77voto

khachik Points 12589

Si vous ne voulez pas utiliser globals, vars et que vous ne voulez pas créer un module et/ou une classe séparés pour encapsuler les fonctions que vous voulez appeler dynamiquement, vous pouvez les appeler en tant qu'attributs du module actuel :

import sys
...
getattr(sys.modules[__name__], "clean_%s" % fieldname)()

0 votes

Notez que cela ne fonctionne pas dans le shell IPython, cependant (mais cela fonctionne dans le shell Python).

0 votes

@EOL Merci, intéressant. Qu'est-ce que __name__ dans le repl d'iPython ?

0 votes

@khachik : __name__ est principal ' à la fois dans les shells Python et IPython. Cependant, sys.modules[__name__] ne contient pas les variables définies dans le shell IPython, mais contient celles définies dans le shell Python. Je me souviens avoir vu de la documentation à ce sujet Ce n'est pas si grave, mais cette différence de comportement entre les deux shells mérite d'être connue

58voto

Jakob Bowyer Points 12873

Utilisation de global est une très, très, très mauvaise façon de faire ça. Vous devriez le faire de cette façon :

fields = {'name':clean_name,'email':clean_email}

for key in fields:
    fields[key]()

Faites correspondre vos fonctions aux valeurs d'un dictionnaire.

Utilisant également vars()[] a tort aussi.

0 votes

J'aime vos champs. dict mais pourriez-vous préciser ce qui ne va pas avec globals() y vars() ?

5 votes

Parce que vars() et globals() devraient être évités, ils sont considérés comme non pythiques et ils sont généralement quelque chose qui nécessite la manipulation de chaînes de caractères ou quelque chose qui complique excessivement le problème. C'est la réponse la plus simple à votre problème.

3 votes

L'utilisation de globals() exposera tout ce qui se trouve dans cette portée et au-dessus, créant potentiellement un risque de sécurité permettant à quiconque peut accéder au dictionnaire retourné par globals() de manipuler son contenu. Pour accéder à quelques membres d'une portée, vous exposerez tout, alors que si vous définissez explicitement le dictionnaire pour qu'il ne contienne que les fonctions nécessaires, il sera plus sûr et plus facile à comprendre pour les autres programmeurs. Et vars() n'est que légèrement meilleur car il n'expose pas tout ce qui se trouve au-dessus de votre portée, mais seulement ce qui se trouve dans la portée actuelle.

50voto

Il serait préférable de disposer d'un dictionnaire de ces fonctions plutôt que de chercher dans globals() .

L'approche habituelle consiste à écrire une classe avec de telles fonctions :

class Cleaner(object):
    def clean_name(self):
        pass

et ensuite utiliser getattr pour y avoir accès :

cleaner = Cleaner()
for f in fields:
    getattr(cleaner, 'clean_%s' % f)()

Vous pourriez même aller plus loin et faire quelque chose comme ceci :

class Cleaner(object):
    def __init__(self, fields):
        self.fields = fields

    def clean(self):
        for f in self.fields:
            getattr(self, 'clean_%s' % f)()

Alors héritez-en et déclarez votre clean_<name> sur une classe héritée :

cleaner = Cleaner(['one', 'two'])
cleaner.clean()

En fait, il est possible de l'étendre encore plus pour le rendre plus propre. La première étape consistera probablement à ajouter une vérification avec le paramètre hasattr() si une telle méthode existe dans votre classe.

11voto

TheHowlingHoaschd Points 127

J'ai rencontré ce problème à deux reprises et j'ai finalement trouvé une solution. seguro y pas moche solution (à mon humble avis).

RECAP des réponses précédentes :

globaux est la méthode la plus simple et la plus rapide, mais vous devez être très cohérent avec les noms de vos fonctions, et elle peut être interrompue au moment de l'exécution si des variables sont écrasées. C'est également non pythique, non sûr, non éthique, etc....

Dictionnaires (i.e. string-to-function maps) sont plus sûrs et faciles à utiliser... mais cela m'ennuie au plus haut point de devoir répartir les assignations de dictionnaires à travers mon fichier, qui sont faciles à perdre de vue.

Décorateurs m'a permis de trouver la solution du dictionnaire. Les décorateurs sont une façon élégante d'attacher des effets secondaires et des transformations à une définition de fonction.

Exemple de temps

fields = ['name', 'email', 'address']

# set up our function dictionary
cleaners = {}

# this is a parametered decorator
def add_cleaner(key):
    # this is the actual decorator
    def _add_cleaner(func):
        cleaners[key] = func
        return func
    return _add_cleaner

Chaque fois que vous définissez une fonction de nettoyage, ajoutez ceci à la déclaration :

@add_cleaner('email')
def email_cleaner(email):
    #do stuff here
    return result

Les fonctions sont ajoutées au dictionnaire dès que leur définition est analysée et peuvent être appelées comme suit :

cleaned_email = cleaners['email'](some_email)

Alternative proposée par PeterSchorn :

def add_cleaner(func):
    cleaners[func.__name__] = func
    return func

@add_cleaner
def email():
   #clean email

Celui-ci utilise le nom de la fonction de la méthode de nettoyage comme clé du dictionnaire. C'est plus concis, bien que je pense que les noms des méthodes deviennent un peu gênants. Choisissez votre préférée.

10voto

Magnus Hoff Points 12052

globals() vous donnera un dict de l'espace de nom global. A partir de là, vous pouvez obtenir la fonction que vous voulez :

f = globals()["clean_%s" % field]

Alors appelez-le :

f()

3 votes

Ou directement globals()['clean_%s' % field]() .

0 votes

+1 parce qu'il fonctionne dans le shell IPython et travaille à l'intérieur des fonctions, par opposition à l'approche vars() approche.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X