2011-07-21 24 views
8

Estoy tratando de crear un objeto con un método de ejecución que estará envuelto por un método _wrap_run. Me gustaría poder llamar al método y a su envoltorio simplemente escribiendo instance.run() y me gustaría poder subclasificar el objeto para poder anular el método run() y hacer que siga ejecutando el contenedor .Método de clase Python Wrap

Más simplemente, quiero que la gente sea capaz de subclase A y anular run(), pero todavía tienen las llamadas al método run() ejecutar la función de contenedor.

Tengo algunas dificultades con la mecánica de esto. ¿Alguien tiene alguna sugerencia con respecto a este enfoque? Gracias por leer.

class A: 

    def run(self): 
     print "Run A" 
     return True 

    def _wrap_run(self): 
     print "PRE" 
     return_value = self.run() 
     print "POST" 
     return return_value 

    run = property(_wrap_run) 


a = A() 
a.run() 
""" 
Should Print: 
PRE 
Run A 
POST 
""" 


class B(A): 

    def run(self): 
     print "Run B" 
     return True 

b = B() 
b.run() 
""" 
Should Print: 
PRE 
Run B 
POST 
""" 
+1

¿Cuál es el punto de '_wrap_run'? ¿Se supone que es una especie de decorador? ¿Qué hay de malo con usar un decorador para esto? –

+1

Él no quiere tener que decorar todas las clases derivadas. – agf

Respuesta

12

Usa una metaclase.

class MetaClass(type): 
    @staticmethod 
    def wrap(run): 
     """Return a wrapped instance method""" 
     def outer(self): 
      print "PRE", 
      return_value = run(self) 
      print "POST" 
      return return_value 
     return outer 
    def __new__(cls, name, bases, attrs): 
     """If the class has a 'run' method, wrap it""" 
     if 'run' in attrs: 
      attrs['run'] = cls.wrap(attrs['run']) 
     return super(MetaClass, cls).__new__(cls, name, bases, attrs) 

class MyClass(object): 
    """Use MetaClass to make this class""" 
    __metaclass__ = MetaClass 
    def run(self): print 'RUN', 

myinstance = MyClass() 

# Prints PRE RUN POST 
myinstance.run() 

Ahora bien, si otras personas subclasifican MyClass, que seguirá recibiendo sus métodos run() envueltos.

+0

Este es un enfoque realmente genial y parece funcionar muy bien. –

3

La manera más fácil: hacer run la envoltura, y un método privado sea el overrideable.

class A(object): 
    def run(self): 
     print "PRE" 
     return_value = self._inner_run() 
     print "POST" 
     return return_value 

    def _inner_run(self): 
     print "Run A" 
     return True 

class B(A): 
    def _inner_run(self): 
     print "Run B" 
     return True 
+0

Gracias por su comentario. Me gustaría hacerlo para que la persona que implementa run() en la subclase no tenga que preocuparse por hacer llamadas adicionales y dependa de ellas para agregar correctamente la llamada a la función. Además, la función de envoltura subrayada está haciendo un trabajo de mantenimiento que me gustaría mantener 'oculto' de la clase infantil. Aunque no sé si lo que estoy pidiendo funcionará ... –

+0

@JoeJ: Documente la interfaz correctamente, y no importará si la subclase tiene que anular 'ejecutar' u otra cosa. –

+0

@ Joe: es muy común para que el usuario 'anulación _run', ver [FormEncode' _to_python'] (http://formencode.org/Validator.html?highlight=_to_python), por ejemplo, en lugar de 'run' – neurino

1

lo que tienes ahí es básicamente un decorator, ¿por qué no seguir adelante y poner en práctica _wrap_run como decorador y aplicarlo cuando la subclasificación de la función?

+0

estaba pensando en hacer un decorador, pero ¿hay alguna manera de hacer la decoración en la clase de padres para que la persona que implementa cualquier clase de niños no tenga que agregar el decorador cada vez? –

+0

No, eso va en contra de lo que significa python –

+0

Creo que es __exactamente__ lo que Python representa. ¿En qué se diferencia de la tipificación de patos o de los gestores de contexto? Puedo escribir 'run()' de manera directa, y la metaclase se ocupa de los detalles. – agf

1

Lo que otras personas hacen

class A: 
    def do_run(self): 
     """Must be overridden.""" 
     raise NotImplementedError 
    def run(self, *args, **kw): 
     """Must not be overridden. 
     You were warned. 
     """ 
     print "PRE" 
     return_value = self.do_run(*args, **kw) 
     print "POST" 
     return return_value 

class B(A): 
    def do_run(self): 
     print "Run B" 
     return True 

Eso es por lo general suficiente.

Si desea preocuparse de que alguien "rompa" esto, deténgase ahora. No pierdas el tiempo preocupándote.

Es Python. Todos somos adultos aquí. Todos los sociópatas maliciosos romperán todo tu código copiándolo, cambiándolo y luego rompiéndolo. No importa lo que hagas, simplemente copiarán tu código y lo modificarán para romper las partes inteligentes.

Todos los demás leerán su comentario y se atendrán a sus reglas. Si quieren usar su módulo/paquete/marco, cooperarán.

+0

'run()' necesita llamar a 'do_run()' que necesita aceptar argumentos. – agf

+0

Gracias S.Lott. Eso parece un enfoque estrecho. Ya, supongo que unos pocos comentarios bien colocados serían suficientes. –

+0

@Joe J: Ellos ** son ** suficientes. No hay nada más que puedas hacer, realmente. Algo más complejo es solo un dolor de cabeza de mantenimiento. Y no importa la cantidad de código adicional que escriba, alguien siempre se va a meter con eso. Así que escriba el código mínimo con el que pueda salirse con la suya. –