web-dev-qa-db-fra.com

Création de méthodes dynamiques/d'exécution (génération de code) en Python

J'ai besoin de générer du code pour une méthode au moment de l'exécution. Il est important de pouvoir exécuter du code arbitraire et une docstring.

Je suis venu avec une solution combinant exec et setattr, voici un exemple factice:

class Viking(object):
    def __init__(self):
        code = '''
            def dynamo(self, arg):
                """ dynamo's a dynamic method!
                """
                self.weight += 1
                return arg * self.weight
            '''
        self.weight = 50

        d = {}
        exec code.strip() in d
        setattr(self.__class__, 'dynamo', d['dynamo'])


if __== "__main__":
    v = Viking()
    print v.dynamo(10)
    print v.dynamo(10)
    print v.dynamo.__doc__

Existe-t-il un moyen meilleur/plus sûr/plus idiomatique de parvenir au même résultat?

41
Eli Bendersky

Basé sur le code de Theran, mais en l'étendant aux méthodes sur les classes:



class Dynamo(object):
    pass

def add_dynamo(cls,i):
    def innerdynamo(self):
        print "in dynamo %d" % i
    innerdynamo.__doc__ = "docstring for dynamo%d" % i
    innerdynamo.__= "dynamo%d" % i
    setattr(cls,innerdynamo.__name__,innerdynamo)

for i in range(2):
    add_dynamo(Dynamo, i)

d=Dynamo()
d.dynamo0()
d.dynamo1()

Ce qui devrait imprimer:


in dynamo 0
in dynamo 1
69
John Montgomery

Les documents de fonction et les noms sont des propriétés mutables. Vous pouvez faire ce que vous voulez dans la fonction interne, ou même avoir plusieurs versions de la fonction interne que makedynamo () choisit. Pas besoin de construire du code avec des chaînes.

Voici un extrait de l'interprète:

>>> def makedynamo(i):
...     def innerdynamo():
...         print "in dynamo %d" % i
...     innerdynamo.__doc__ = "docstring for dynamo%d" % i
...     innerdynamo.__= "dynamo%d" % i
...     return innerdynamo

>>> dynamo10 = makedynamo(10)
>>> help(dynamo10)
Help on function dynamo10 in module __main__:

dynamo10()
    docstring for dynamo10
12
Theran

Python vous laissera déclarer une fonction dans une fonction, vous n’aurez donc pas à vous tromper exec.

def __init__(self):

    def dynamo(self, arg):
        """ dynamo's a dynamic method!
        """
        self.weight += 1
        return arg * self.weight
    self.weight = 50

    setattr(self.__class__, 'dynamo', dynamo)

Si vous souhaitez avoir plusieurs versions de la fonction, vous pouvez mettre tout cela en boucle et modifier le nom que vous leur donnez dans la fonction setattr:

def __init__(self):

    for i in range(0,10):

        def dynamo(self, arg, i=i):
            """ dynamo's a dynamic method!
            """
            self.weight += i
            return arg * self.weight

        setattr(self.__class__, 'dynamo_'+i, dynamo)
        self.weight = 50

(Je sais que ce n'est pas un bon code, mais il fait passer le message). En ce qui concerne la configuration de la docstring, je sais que c'est possible, mais il faudrait que je vérifie dans la documentation.

Edit : Vous pouvez définir la docstring via dynamo.__doc__, de sorte que vous puissiez faire quelque chose comme ceci dans votre corps de boucle:

dynamo.__doc__ = "Adds %s to the weight" % i

Another Edit : Avec l’aide de @eliben et @bobince, le problème de fermeture devrait être résolu.

8
Justin Voss
class Dynamo(object):
    def __init__(self):
        pass

    @staticmethod
    def init(initData=None):
        if initData is not None:
            dynamo= Dynamo()
            for name, value in initData.items():
                code = '''
def %s(self, *args, **kwargs):
%s
                            ''' % (name, value)
                result = {}
                exec code.strip() in result
                setattr(dynamo.__class__, name, result[name])

            return dynamo

        return None

service = Dynamo.init({'fnc1':'pass'})
service.fnc1()
0
Alexander