2010-03-30 12 views
7

Estoy tratando de encontrar la forma de usar decoradores en subclases que usan super(). Como mi decorador de clases crea otra subclase, una clase decorada parece impedir el uso de super() cuando cambia el className pasado al super(className, self). A continuación se muestra un ejemplo:Decoradores de clase, Herencia, súper() y recursividad máxima

def class_decorator(cls): 
    class _DecoratedClass(cls): 
     def __init__(self): 
      return super(_DecoratedClass, self).__init__() 
    return _DecoratedClass 

class BaseClass(object): 
    def __init__(self): 
     print "class: %s" % self.__class__.__name__ 
    def print_class(self): 
     print "class: %s" % self.__class__.__name__ 

bc = BaseClass().print_class() 

class SubClass(BaseClass): 
    def print_class(self): 
     super(SubClass, self).print_class() 

sc = SubClass().print_class() 

@class_decorator 
class SubClassAgain(BaseClass): 
    def print_class(self): 
     super(SubClassAgain, self).print_class() 

sca = SubClassAgain() 
# sca.print_class() # Uncomment for maximum recursion 

La salida debe ser:

class: BaseClass 
class: BaseClass 
class: SubClass 
class: SubClass 
class: _DecoratedClass 
Traceback (most recent call last): 
File "class_decorator_super.py", line 34, in <module> 
sca.print_class() 
File "class_decorator_super.py", line 31, in print_class 
super(SubClassAgain, self).print_class() 
... 
... 
RuntimeError: maximum recursion depth exceeded while calling a Python object 

¿Alguien sabe de una manera de no romper una subclase que utiliza super() cuando se utiliza un decorador? Idealmente, me gustaría reutilizar una clase de vez en cuando y simplemente decorarla sin romperla.

Respuesta

3

El decorador crea una especie de herencia de diamantes. Puede evitar estos problemas al no usar super(). Cambiando SubClassAgain a la siguiente evitará la repetición infinita:

@class_decorator 
class SubClassAgain(BaseClass): 
    def print_class(self): 
     BaseClass.print_class(self) 
+0

¡Gracias por su respuesta! Seleccioné esta respuesta porque no une la subclase con el decorador. Me di cuenta de que mi pregunta era sobre cómo usar super(), pero creo que la respuesta es no. – jamstooks

2

¿Estás seguro de que deseas utilizar un decorador de clases y no simplemente la herencia? Por ejemplo, en lugar de un decorador para reemplazar su clase con una subclase que introduce algunos métodos, ¿quizás quiere una clase mixin y usar herencia múltiple para crear la clase final?

Esto se lograría mediante algo así como

class MyMixIn(object): 
    def __init__(self): 
     super(MyMixIn, self).__init__() 

class BaseClass(object): 
    def __init__(self): 
     print "class: %s" % self.__class__.__name__ 
    def print_class(self): 
     print "class: %s" % self.__class__.__name__ 

class SubClassAgain(BaseClass, MyMixIn): 
    def print_class(self): 
     super(SubClassAgain, self).print_class() 

sca = SubClassAgain() 
sca.print_class() 
+0

Me gustó mucho esta solución. Sin embargo, utilizo algunos decoradores de clase que toman un parámetro (permiso). Podría usar la combinación para cambiar los parámetros __init__, pero creo que prefiero la flexibilidad de simplemente agregar @decorator (permanente) a la clase en lugar de tener que cambiar mi código cuando quiero iniciar una clase con el parámetro de permiso. ¡Puedo usar esto en lugar de otros decoradores! – jamstooks

+0

Debido a las metaclases, es técnicamente posible utilizar mezclas para * cualquier cosa * para las que pueda usar un decorador de clases. En general, uso mixins si quiero agregar métodos a una clase y uso decoradores de clase cuando realmente no quiero mutar una clase, sino simplemente registrarla o algo así. –

+0

¡Gracias, Mike! Eso suena como una buena metodología. ¿Cómo le pasaría el permiso al mixin? ¿Lo haría una propiedad de la subclase cuando lo declaro? – jamstooks

5

Básicamente, se puede ver el problema después de introducir su código de ejemplo en el indicador de Python interactivo:

>>> SubClassAgain 
<class '__main__._DecoratedClass'> 

es decir, el nombre SubClassAgain es ahora obligado (en el alcance global, en este caso) a una clase que, de hecho, no es "real" SubClassAgain, sino una subclase de la misma. Por lo tanto, cualquier referencia de destino tardío a ese nombre, como la que tiene en su llamada super(SubClassAgain,, obtendrá la subclase que se enmascara con ese nombre, la superclase de esa subclase es, por supuesto, "la real SubClassAgain", de ahí que la recursión infinita .

puede reproducir el mismo problema muy simple y sin decoración, sólo por tener cualquier subclase usurpar el nombre de su de la base de clase:

>>> class Base(object): 
... def pcl(self): print 'cl: %s' % self.__class__.__name__ 
... 
>>> class Sub(Base): 
... def pcl(self): super(Sub, self).pcl() 
... 
>>> Sub().pcl() 
cl: Sub 
>>> class Sub(Sub): pass 
... 

ahora, Sub().pcl() hará que la recursividad infinita, debido al "nombre de usurpación". La decoración de clase, a menos que la uses para decorar y devolver la misma clase que obtienes como argumento, es una "usurpación de nombre" sistemática, y por lo tanto incompatible con los usos del nombre de clase que debe devolver la clase "verdadera" de ese nombre, y no el usurpador (ya sea en el self o de otro modo).

Soluciones provisionales - si es absolutamente necesario tener tanto la decoración como la usurpación de clase (no sólo la decoración de clase por los cambios en el argumento de clase recibida), ysuper - básicamente necesitan protocolos de cooperación entre el usurpador y el posible-usurpee , tales como los siguientes pequeños cambios en el código de ejemplo:

def class_decorator(cls): 
    class _DecoratedClass(cls): 
    _thesuper = cls 
     def __init__(self): 
      return super(_DecoratedClass, self).__init__() 
    return _DecoratedClass 

    ... 

@class_decorator 
class SubClassAgain(BaseClass): 
    def print_class(self): 
    cls = SubClassAgain 
    if '_thesuper' in cls.__dict__: 
     cls = cls._thesuper 
     super(cls, self).print_class() 
2

como probablemente ya saben, el problema surge del hecho de que el nombre SubClassAgain en SubClassAgain.print_class está en el ámbito de espacio de nombres global del módulo actual. SubClassAgain por lo tanto, se refiere a la clase _DecoratedClass en lugar de la clase que se decora. Una forma de llegar a la clase decorada es seguir una convención que los decoradores de la clase tienen una propiedad que se refiere a la clase decorada.

def class_decorator(cls): 
    class _DecoratedClass(cls): 
     original=cls 
     def __init__(self): 
      print '_DecoratedClass.__init__' 
      return super(_DecoratedClass, self).__init__() 
    return _DecoratedClass 

@class_decorator 
class SubClassAgain(BaseClass): 
    original 
    def print_class(self): 
     super(self.__class__.original, self).print_class() 

Otra es utilizar la propiedad __bases__ para obtener la clase decorada.

@class_decorator 
class SubClassAgain(BaseClass): 
    def print_class(self): 
     super(self.__class__.__bases__[0], self).print_class() 

Por supuesto, con múltiples decoradores, cualquiera de estos se vuelve difícil de manejar. Este último tampoco funciona con subclases de la clase decorada. Puede combinar decoradores y mixins, escribiendo un decorador que agregue un mixin en una clase. Esto no lo ayudará a anular los métodos.

def class_decorator(cls): 
    class _DecoratedClass(object): 
     def foo(self): 
      return 'foo' 
    cls.__bases__ += (_DecoratedClass,) 
    return cls 

Por último, puede trabajar directamente con los atributos de clase para establecer los métodos.

def class_decorator(cls): 
    old_init = getattr(cls, '__init__') 
    def __init__(self, *args, **kwargs): 
     print 'decorated __init__' 
     old_init(self, *args, **kwargs) 
    setattr(cls, '__init__', __init__) 
    return cls 

Esta es probablemente la mejor opción para su ejemplo, aunque el decorador basado en mixin también tiene sus aplicaciones.

0

¿Qué hay de la simple promoción de _DecoratedClass 's __bases__ hasta el __bases__ de SubClassAgain?

def class_decorator(cls): 
    class _DecoratedClass(cls): 
     def __init__(self): 
      return super(_DecoratedClass, self).__init__() 
    _DecoratedClass.__bases__=cls.__bases__ 
    return _DecoratedClass