2011-03-07 7 views
5

Tengo un problema en Python, por lo cual no puedo encontrar ninguna solución limpia ...Python: métodos metaclase + envueltos + herencia = problemas

Al llamar a algunos métodos, quiero ejecutar algún código antes de la ejecución del método y después. En orden (entre muchas otras cosas) para establecer y limpiar automáticamente una variable context.

Para lograr esto, se han declarado lo siguiente metaclase:

class MyType(type): 
    def __new__(cls, name, bases, attrs): 
     #wraps the 'test' method to automate context management and other stuff 
     attrs['test'] = cls.other_wrapper(attrs['test']) 
     attrs['test'] = cls.context_wrapper(attrs['test']) 
     return super(MyType, cls).__new__(cls, name, bases, attrs) 

    @classmethod 
    def context_wrapper(cls, operation): 
     def _manage_context(self, *args, **kwargs): 
      #Sets the context to 'blabla' before the execution 
      self.context = 'blabla' 
      returned = operation(self, *args, **kwargs) 
      #Cleans the context after execution 
      self.context = None 
      return returned 
     return _manage_context 

    @classmethod 
    def other_wrapper(cls, operation): 
     def _wrapped(self, *args, **kwargs): 
      #DO something with self and *args and **kwargs 
      return operation(self, *args, **kwargs) 
     return _wrapped 

Esto funciona como un encanto:

class Parent(object): 

    __metaclass__ = MyType 

    def test(self): 
     #Here the context is set: 
     print self.context #prints blabla 

Pero tan pronto como quiero subclase Parent, aparecen problemas, cuando llamo al método principal con super:

class Child(Parent): 
    def test(self): 
     #Here the context is set too 
     print self.context #prints blabla 
     super(Child, self).test() 
     #But now the context is unset, because Parent.test is also wrapped by _manage_context 
     #so this prints 'None', which is not what was expected 
     print self.context 

He pensado en guardar el contexto antes de establecerlo en un nuevo valor, pero eso solo resuelve parcialmente el problema ...

De hecho, (espere, esto es difícil de explicar), se llama el método principal, el envolturas se ejecutan, pero reciben *args y **kwargs dirigidas a Parent.test, mientras self es una instancia Child, por lo self atributos tienen valores irrelevantes si quiero retarlos con *args y **kwargs (por ejemplo, para fines de validación automatizado), ejemplo:

@classmethod 
def validation_wrapper(cls, operation): 
    def _wrapped(self, *args, **kwargs): 
     #Validate the value of a kwarg 
     #But if this is executed because we called super(Child, self).test(... 
     #`self.some_minimum` will be `Child.some_minimum`, which is irrelevant 
     #considering that we called `Parent.test` 
     if not kwarg['some_arg'] > self.some_minimum: 
      raise ValueError('Validation failed') 
     return operation(self, *args, **kwargs) 
    return _wrapped 

Así que, básicamente, a solv e este problema que veo dos soluciones:

  1. que impiden que los envoltorios para ser ejecutado cuando el método se llama con super(Child, self)

  2. tener un self que siempre es del tipo "correcto"

Ambas soluciones me parecen imposibles ... ¿Alguien tiene una idea sobre cómo resolver esto? Una sugerencia ?

+0

¿Por qué no puedes simplemente usar un decorador para eso? –

+0

¿Está buscando algún tipo de soporte de programación orientado a aspectos en Python? Consulte http://stackoverflow.com/questions/286958/any-aop-support-library-for-python para obtener algunas ideas al respecto. Esto no es exactamente lo que está pidiendo, sino que trata de resolver sus necesidades originales. – Makis

+0

@ Space_C0wb0y: porque quiero poder redeclarar 'test' en una subclase, ¡sin tener que redecorarlo con 5 decoradores! Y de todos modos eso no resolvería mi problema: la forma en que envuelvo los métodos de la metaclase es exactamente equivalente a usar decoradores. – sebpiq

Respuesta

0

De hecho, he descubierto una manera de prevenir los envoltorios para ser ejecutado cuando el método se llama con super(Child, self):

class MyType(type): 
    def __new__(cls, name, bases, attrs): 
     #wraps the 'test' method to automate context management and other stuff 
     new_class = super(MyType, cls).__new__(cls, name, bases, attrs) 
     new_class.test = cls.other_wrapper(new_class.test, new_class) 

    @classmethod 
    def other_wrapper(cls, operation, new_class): 
     def _wrapped(self, *args, **kwargs): 
      #DO something with self and *args and **kwargs ... 
      #ONLY if self is of type *new_class* !!! 
      if type(self) == new_class: 
       pass #do things 
      return operation(self, *args, **kwargs) 
     return _wrapped 

De esta manera, cuando se llama:

super(Child, self).a_wrapped_method 

El código de envoltura es by-passed !!! Eso es bastante hackish, pero funciona ...

1

Bueno, ¿no puedes simplemente verificar si el contexto ya está establecido en _manage_context? De esta manera:

def _manage_context(self, *args, **kwargs): 
    #Sets the context to 'blabla' before the execution 
    if self.context is None: 
     self.context = 'blabla' 
     returned = operation(self, *args, **kwargs) 
     #Cleans the context after execution 
     self.context = None 
     return returned 
    else: 
     return operation(self, *args, **kwargs) 

Además, esto probablemente se debe envolver en un bloque try-catch, para asegurar la reposición del contexto en el caso de las excepciones.

+0

Esa es una idea ... sin embargo, se va a romper si por alguna razón el contexto se establece desde fuera del método. Además, requiere que todas las demás envolturas verifiquen si el contexto ya está establecido antes de la ejecución. – sebpiq

0

Ok, primero, tu "solución" es realmente fea, pero supongo que ya lo sabes. :-) Entonces intentemos responder sus preguntas.

La primera es una "pregunta" implícita: ¿por qué no usa los administradores de contexto de Python? Te dan mucha mejor sintaxis y administración de errores de forma prácticamente gratuita. Vea el módulo contextlib, puede serle de gran ayuda. Especialmente vea section about reentrancy.

Luego verás que la gente suele tener problemas al intentar apilar administradores de contexto. Eso no es sorprendente, ya que para apoyar correctamente la recursión necesita una pila de valores, no un solo valor. [Se podía ver la fuente de algunos de reentrada cm, por ejemplo redirect_stdout, para ver cómo se maneja.] Por lo que su context_wrapper deberán:

  • (limpia) mantener una lista de self.context s, anexar a ella cuando entra contexto, y pop de él al salir. De esa manera siempre obtendrá su contexto.

  • (más bien lo que quiere) mantener una única self.context, sino también un valor global de DEPTH, incrementado en uno en entrar, disminuido por uno a la salida, y volver a poner a ninguno self.context cuando la profundidad es 0.

En cuanto a su segunda pregunta, debo decir que no los entiendo del todo. selfes del tipo correcto. Si A es la subclase de B y el yo es la instancia de A, entonces también es instancia de B. Si self.some_minimum está "mal" si se considera una instancia de A o de B, eso significa que some_minimum no es realmente un atributo de instancia de uno mismo, pero un atributo de clase de A o B. ¿Derecha? Ellos pueden ser libremente diferentes en A y en B, porque A y B son diferentes objetos (de su metaclase).

Cuestiones relacionadas