web-dev-qa-db-fra.com

Comment appeler Python fonctionne dynamiquement

J'ai ce code:

fields = ['name','email']

def clean_name():
    pass

def clean_email():
    pass

Comment appeler dynamiquement clean_name() et clean_email()?

Par exemple:

for field in fields:
    clean_{field}()

J'ai utilisé les accolades car c'est comme ça que je le faisais dans PHP mais ça ne fonctionne évidemment pas.

Comment faire cela avec Python?

46
nemesisdesign

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

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

Il serait préférable d'avoir un dictionnaire de ces fonctions que de regarder dans globals().

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

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

puis utilisez getattr pour y accéder:

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

Vous pouvez 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)()

Puis héritez-le et déclarez vos méthodes clean_<name> Sur une classe héritée:

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

En fait, cela peut être étendu encore plus pour le rendre plus propre. La première étape consistera probablement à ajouter une vérification avec hasattr() si une telle méthode existe dans votre classe.

34
Alexander Solovyov

Utiliser global est une très, très, mauvaise façon de procéder. Vous devriez le faire de cette façon:

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

for key in fields:
    fields[key]()

Mappez vos fonctions aux valeurs d'un dictionnaire.

L'utilisation de vars()[] est également incorrecte.

33
Jakob Bowyer

globals() vous donnera un dict de l'espace de noms global. De là, vous pouvez obtenir la fonction que vous souhaitez:

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

Appelez-le ensuite:

f()
6
Magnus Hoff

Voici une autre façon:

myscript.py:

def f1():
    print 'f1'

def f2():
    print 'f2'

def f3():
    print 'f3'

test.py:

import myscript

for i in range(1, 4):
    getattr(myscript, 'f%d' % i)()
4
demas

J'ai rencontré ce problème deux fois maintenant, et j'ai finalement trouvé une solution sûre et pas laide (à mon humble avis).

RÉCAPITULEZ des réponses précédentes:

globals est la méthode hacky, rapide et facile, mais vous devez être super cohérent avec les noms de vos fonctions, et il peut se casser lors de l'exécution si les variables sont écrasées . C'est aussi non-Pythonic, dangereux, contraire à l'éthique, yadda yadda ...

Les dictionnaires (c'est-à-dire les cartes chaîne-fonction) sont plus sûrs et faciles à utiliser ... mais cela m'ennuie sans fin, que je dois diffuser affectations de dictionnaire dans mon fichier, qui sont faciles à perdre de vue.

Décorateurs a fait en sorte que la solution du dictionnaire soit réunie pour moi. Les décorateurs sont un joli moyen d'attacher des effets secondaires et des transformations à une définition de fonction.

Exemple d'heure

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

# set up our function dictionary
cleaners = {}

# this function will add stuff into the dictionary
def add_cleaner(key):
    # this is a parametered decorator, it returns the actual decorator

    def actual_decorator(func):
        # add func to the dictionary
        cleaners[key] = func
        return func

    return actual_decorator

Chaque fois que vous définissez une fonction plus propre, 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 qu'elles sont analysées et peuvent être appelées comme ceci:

cleaned_email = cleaners['email'](some_email)

Vous voudrez peut-être ajouter cette ligne au bas de votre script, pour vous assurer que vous n'en avez pas oublié une. ;)

assert(set(cleaners.keys()).issubset(fields))
3
TheHowlingHoaschd
for field in fields:
    vars()['clean_' + field]()
2
Matt Joiner

Voici une autre façon: définir les fonctions puis définir un dict avec les noms comme clés:

>>> z=[clean_email, clean_name]
>>> z={"email": clean_email, "name":clean_name}
>>> z['email']()
>>> z['name']()

vous bouclez ensuite les noms sous forme de clés.

ou que diriez-vous celui-ci? Construisez une chaîne et utilisez 'eval':

>>> field = "email"
>>> f="clean_"+field+"()"
>>> eval(f)

puis bouclez et construisez les chaînes pour eval.

Notez que toute méthode qui nécessite la construction d'une chaîne pour l'évaluation est considérée comme kludgy.

2
Spacedman

J'utiliserais un dictionnaire qui associe les noms de champs aux fonctions de nettoyage. Si certains champs n'ont pas de fonction de nettoyage correspondante, la boucle for qui les gère peut être simple en fournissant une sorte de fonction par défaut pour ces cas. Voici ce que je veux dire:

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

def clean_name():
    pass
def clean_email():
    pass

# (one-time) field to cleaning-function map construction
def get_clean_func(field):
    try:
        return eval('clean_'+field)
    except NameError:
        return lambda: None  # do nothing
clean = dict((field, get_clean_func(field)) for field in fields)

# sample usage
for field in fields:
    clean[field]()

Le code ci-dessus construit dynamiquement le dictionnaire de fonctions en déterminant si une fonction correspondante nommée clean_<field> existe pour chacun nommé dans la liste fields. Vous n'auriez probablement à l'exécuter qu'une seule fois car il resterait le même tant que la liste des champs ou les fonctions de nettoyage disponibles ne sont pas modifiées.

1
martineau