2009-02-10 21 views
37

Necesito generar código para un método en tiempo de ejecución. Es importante poder ejecutar código arbitrario y tener una docstring.Creación de métodos dinámicos/en tiempo de ejecución (generación de código) en Python

me ocurrió una solución que combina exec y setattr, aquí hay un ejemplo ficticio:

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__ 

¿Hay una manera mejor/más seguro/más idiomática de lograr el mismo resultado?

+0

¿Por qué necesita eso, ¿se tiene en cuenta el resto de instalaciones metaprogramación en Python? –

+0

Estoy abierto a sugerencias :-) Necesito esto para generar reglas para PLY, que las necesita como métodos con docstrings. Para automatizar un código repetitivo, puedo generar algunas reglas en un ciclo en tiempo de ejecución –

+0

¿Puedes dar un mejor ejemplo o explicar más? El ejemplo que das no es muy dinámico ya que es una cadena codificada, tengo problemas para entender por qué no puedes usar despachadores, polimorfismo, metaclases, etc. –

Respuesta

63

basado en el código de Theran, pero extendiéndolo a los métodos de las clases:

 


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

 

Cuál debería imprimir:

 

in dynamo 0 
in dynamo 1 
 
+0

Gracias, esto funciona muy bien. De hecho, en este caso se puede ahorrar el 'ejecutivo', pero solo porque el código del método es relativamente constante y no * en realidad * (las cadenas impresas no cuentan) depende del método en sí –

+0

, la solución es buena para la pregunta, pero sería más útil poner todo en una sola clase clase Dynamo (objeto): – igni

+0

¿Qué pasa si quiero agregar un decorador a cada función? Cómo puedo hacer eso ? Intenté algo así como usar @my_decorator justo encima de la función innerdynamo. Pero no funcionó. Alguna sugerencia ? – user699540

6

Python le permitirá declarar una función en una función, por lo que no tiene que hacer el truco 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 usted quiere tener varias versiones de la función, se puede poner todo esto en un bucle y variar lo que les nombre en la función 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 

(sé que esto no es es un gran código, pero hace entender el punto). En cuanto a la configuración de la docstring, sé que es posible, pero tendría que buscarla en la documentación.

Editar: Puede establecer la cadena de documentación a través de dynamo.__doc__, por lo que podría hacer algo como esto en su cuerpo del ciclo:

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

Otra Editar: Con la ayuda de @eliben y @bobince, la problema de cierre debe ser resuelto.

+0

'i' será 10 en cada instancia de dynamo una vez que el ciclo haya terminado. La variable es no rebote cada vez que pasa el ciclo. Esta es una de las grandes trampas sobre el uso de cierres en Python (y otros lenguajes similares). – bobince

+0

Ah, gracias Gracias por la aclaración. ¿Hay alguna técnica que funcione? –

+1

Justin, por el solución de este gotcha ver: http://stackoverflow.com/questions/233673/lexical-closures-in-python/235764#235764 –

11

La función docstrings y los nombres son propiedades mutables. Puede hacer lo que quiera en la función interna, o incluso tener múltiples versiones de la función interna que macedynamo() elija entre. No es necesario construir ningún código de cadenas.

He aquí un fragmento de la intérprete:

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

¿cómo se hace esto para los métodos? –

-1

Perdón por mi mal inglés.

Recientemente necesito generar una función dinámica para vincular cada elemento del menú para abrir un marco particular en wxPython. Esto es lo que hago.

primero, creo una lista de asignación entre el elemento de menú y el marco.

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

el primer elemento en la asignación es el elemento del menú y el último elemento es el marco que se va a abrir. A continuación, ato el evento wx.EVT_MENU desde cada elemento del menú a un marco particular.

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

función genfunc es el constructor función dinámica, aquí está el código:

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