52 votes

Création de méthodes dynamiques/temps réel (génération de code) en Python

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

J'ai trouvé une solution combinant exec y setattr Voici un exemple fictif :

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 __name__ == "__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 d'obtenir le même résultat ?

0 votes

Pourquoi avez-vous besoin de cela, avez-vous considéré les autres facilités de métaprogrammation de Python ?

0 votes

Je suis ouvert aux suggestions :-) J'en ai besoin pour générer des règles pour PLY, qui en a besoin en tant que méthodes avec des docstrings. Pour automatiser un peu de code standard, je peux générer des règles dans une boucle au moment de l'exécution.

0 votes

Pouvez-vous donner un meilleur exemple ou expliquer davantage ? L'exemple que vous donnez n'est pas très dynamique puisqu'il s'agit d'une chaîne codée en dur, j'ai du mal à comprendre pourquoi vous ne pouvez pas utiliser les répartiteurs, le polymorphisme, les métaclasses, etc.

82voto

John Montgomery Points 3770

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.__name__ = "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 s'imprimer :

in dynamo 0
in dynamo 1

0 votes

Merci, cela fonctionne bien. En effet, dans ce cas, le "exec" peut être épargné - mais seulement parce que le code de la méthode est relativement constant et n'a pas besoin d'être modifié. vraiment (les chaînes imprimées ne comptent pas) dépendent de la méthode elle-même

1 votes

La solution est bonne pour la question, mais il serait plus utile de tout mettre dans une seule classe la classe Dynamo(objet) :

0 votes

Que faire si je veux ajouter un décorateur à chaque fonction. Comment faire ? J'ai essayé quelque chose comme utiliser @my_decorator juste au dessus de la fonction innerdynamo. Mais cela n'a pas fonctionné. Des suggestions ?

14voto

Theran Points 2605

Les docstrings et les noms des fonctions 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 entre lesquelles makedynamo() choisit. Il n'est pas nécessaire de construire du code à partir de chaînes de caractères.

Voici un extrait de l'interpréteur :

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

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

dynamo10()
    docstring for dynamo10

11voto

Justin Voss Points 2407

Python vous permet de déclarer une fonction dans une fonction, de sorte que vous n'avez pas besoin de faire l'opération exec la tromperie.

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 voulez avoir plusieurs versions de la fonction, vous pouvez mettre tout cela dans une boucle et varier le nom que vous leur donnez dans la fonction setattr fonction :

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 très bon code, mais cela permet de faire passer le message). En ce qui concerne la définition de la chaîne de caractères, je sais que c'est possible, mais il faudrait que je vérifie dans la documentation.

Editer : Vous pouvez définir la docstring via dynamo.__doc__ Vous pouvez donc faire quelque chose comme cela dans le corps de votre boucle :

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

Autre édition : Avec l'aide de @eliben et @bobince, le problème de fermeture devrait être résolu.

0 votes

I' sera de 10 dans chaque instance de dynamo une fois la boucle terminée. La variable n'est pas rebondie à chaque tour de boucle. C'est l'un des principaux problèmes liés à l'utilisation des fermetures en Python (et dans d'autres langages similaires).

0 votes

Ah, zut. Merci pour cette précision. Existe-t-il une technique qui fonctionne ?

1 votes

Justin, pour la solution de ce problème, voir : stackoverflow.com/questions/233673/lexical-closures-in-python/

1voto

Alexander Points 39
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()

0voto

Markus Dutschke Points 1214

Une solution un peu plus générale :

Vous pouvez appeler n'importe quelle méthode d'une instance de la classe Dummy. La docstring est générée sur la base du nom de la méthode. La gestion des arguments d'entrée est démontrée, en les renvoyant simplement.

C

class Dummy(object):

    def _mirror(self, method, *args, **kwargs):
        """doc _mirror"""
        return args, kwargs

    def __getattr__(self, method):
        "doc __getattr__"

        def tmp(*args, **kwargs):
            """doc tmp"""
            return self._mirror(method, *args, **kwargs)
        tmp.__doc__ = (
                'generated docstring, access by {:}.__doc__'
                .format(method))
        return tmp

d = Dummy()    
print(d.test2('asd', level=0), d.test.__doc__)
print(d.whatever_method(7, 99, par=None), d.whatever_method.__doc__)

Sortie

(('asd',), {'level': 0}) generated docstring, access by test.__doc__
((7, 99), {'par': None}) generated docstring, access by whatever_method.__doc__

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