2009-02-10 9 views
37

Ho bisogno di generare codice per un metodo in fase di esecuzione. È importante essere in grado di eseguire codice arbitrario e disporre di una docstring.Creazione metodo dinamico/runtime (generazione codice) in Python

mi si avvicinò con una soluzione che combina exec e setattr, ecco un esempio fittizio:

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__ 

C'è un// modo più idiomatico sicuro meglio di ottenere lo stesso risultato?

+0

Perché avete bisogno di questo, ha fatto si considerano le altre strutture metaprogrammazione in Python? –

+0

Sono aperto ai suggerimenti :-) Ho bisogno di questo per generare regole per PLY, che ha bisogno di loro come metodi con docstring. Per automatizzare un codice boilerplate, posso generare alcune regole in un loop in fase di esecuzione –

+0

Puoi dare un esempio migliore o spiegarne altre? L'esempio che fornisci non è molto dinamico poiché è una stringa hard coded, ho difficoltà a capire perché non puoi usare dispatcher, polimorfismi, metaclassi, ecc. –

risposta

63

Basato sul codice di Theran, ma estendendolo ai metodi sulle classi:

 


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

 

che dovrebbe stampare:

 

in dynamo 0 
in dynamo 1 
 
+0

Grazie, questo funziona bene. Infatti, in questo caso l''exec' può essere risparmiato - ma solo perché il codice del metodo è relativamente costante e non * veramente * (le stringhe stampate non contano) dipende dal metodo stesso –

+0

la soluzione è buona per la domanda, ma sarebbe più utile mettere tutto in una classe singola classe Dinamo (oggetto): – igni

+0

Cosa succede se voglio aggiungere un decoratore a ciascuna funzione. Come lo posso fare ? Ho provato qualcosa come usare @my_decorator proprio sopra la funzione innerdynamo. Ma non ha funzionato. Eventuali suggerimenti ? – user699540

6

Python consente di dichiarare una funzione in una funzione, quindi non è necessario eseguire gli inganni 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) 

Se si vuole avere diverse versioni della funzione, si può mettere tutto questo in un ciclo e variare ciò che li nome nella funzione 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 

(so che questo isn 'ottimo codice, ma ottiene il punto attraverso). Per quanto riguarda l'impostazione della docstring, so che è possibile ma dovrei cercare nella documentazione.

Modifica: è possibile impostare la docstring via dynamo.__doc__, così si potrebbe fare qualcosa di simile nel vostro corpo del ciclo:

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

Un'altra Edit: Con l'aiuto di @eliben e @bobince, il problema di chiusura dovrebbe essere risolto.

+0

" i "sarà 10 in ogni istanza di dynamo una volta che il ciclo è terminato. Non rimbalzo ogni volta intorno al loop.Questo è uno dei grandi trucchi sull'utilizzo di chiusure in Python (e in altre lingue simili) – bobince

+0

Ah, drat Grazie per i chiarimenti C'è una tecnica che funzionerà? –

+1

Justin, per il la soluzione di questo risultato è disponibile all'indirizzo: http://stackoverflow.com/questions/233673/lexical-closures-in-python/235764#235764 –

11

Le didascalie e i nomi delle funzioni sono proprietà modificabili. Puoi fare tutto ciò che vuoi nella funzione interiore, o anche avere più versioni della funzione interiore scelta da makedynamo(). Non c'è bisogno di creare alcun codice con le stringhe.

Ecco un frammento fuori l'interprete:

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

come funziona questo metodo? –

-1

Perdonami per il mio pessimo inglese.

Recentemente ho bisogno di generare una funzione dinamica per collegare ciascuna voce di menu per aprire un particolare frame su wxPython. Ecco cosa faccio.

Per prima cosa, creo un elenco di mappe tra la voce di menu e la cornice.

menus = [(self.menuItemFile, FileFrame), (self.menuItemEdit, EditFrame)] 

il primo elemento sulla mappatura è la voce di menu e l'ultimo elemento è la cornice da aprire. Successivamente, associo l'evento wx.EVT_MENU da ciascuna voce di menu a un particolare fotogramma.

for menu in menus: 
    f = genfunc(self, menu[1]) 
    self.Bind(wx.EVT_MENU, f, menu[0]) 

funzione genfunc è il costruttore funzione dinamica, ecco il codice:

def genfunc(parent, form): 
    def OnClick(event): 
     f = form(parent) 
     f.Maximize() 
     f.Show() 
    return OnClick 
0
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()