56 Stimmen

Erstellung von dynamischen/Laufzeitmethoden (Codegenerierung) in Python

Ich muss Code für eine Methode zur Laufzeit generieren. Es ist wichtig, dass ich beliebigen Code ausführen kann und einen Docstring habe.

Ich habe eine Lösung gefunden, die exec y setattr Hier ist ein Beispiel für einen Dummy:

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__

Gibt es einen besseren / sichereren / idiomatischeren Weg, um das gleiche Ergebnis zu erzielen?

87voto

John Montgomery Punkte 8418

Basierend auf dem Code von Theran, aber erweitert um Methoden für Klassen:

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

Das sollte gedruckt werden:

in dynamo 0
in dynamo 1

14voto

Theran Punkte 3746

Funktions-Docstrings und -Namen sind veränderbare Eigenschaften. Sie können in der inneren Funktion alles tun, was Sie wollen, oder sogar mehrere Versionen der inneren Funktion haben, zwischen denen makedynamo() wählt. Sie müssen keinen Code aus Zeichenketten erstellen.

Hier ist ein Ausschnitt aus dem Interpreter:

>>> 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

12voto

Justin Voss Punkte 6174

In Python können Sie eine Funktion in einer Funktion deklarieren, so dass Sie nicht die exec Betrug.

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)

Wenn Sie mehrere Versionen der Funktion haben möchten, können Sie all dies in eine Schleife einfügen und die Namen in der setattr Funktion:

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

(Ich weiß, das ist kein großartiger Code, aber er bringt es auf den Punkt). Soweit wie die Einstellung der docstring, ich weiß, dass es möglich ist, aber ich würde es in der Dokumentation zu suchen haben.

Editar : Sie können den Docstring über dynamo.__doc__ Sie könnten also etwas Ähnliches in Ihrem Schleifenkörper tun:

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

Eine weitere Bearbeitung : Mit Hilfe von @eliben und @bobince sollte das Schließungsproblem gelöst werden.

1voto

Alexander Punkte 113
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 Punkte 6917

Eine etwas allgemeinere Lösung:

Sie können jede Methode einer Instanz der Klasse Dummy aufrufen. Der Docstring wird auf der Grundlage des Methodennamens generiert. Die Behandlung von Eingabeargumenten wird demonstriert, indem diese einfach zurückgegeben werden.

Code

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__)

Ausgabe

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X