2012-08-04 12 views
5

Recientemente leí an interesting discussion on how to make a singleton in Python. Una de las soluciones fue un tricky decorator defining a class inside its code as a substitute for decorated class:¿Por qué ocurre una recursión aquí?

def singleton(class_): 
    class class_w(class_): 
     _instance = None 
     def __new__(class2, *args, **kwargs): 
      if class_w._instance is None: 
       class_w._instance = super(class_w, class2).__new__(class2, *args, **kwargs) 
       class_w._instance._sealed = False 
      return class_w._instance 
     def __init__(self, *args, **kwargs): 
      if self._sealed: 
       return 
      super(class_w, self).__init__(*args, **kwargs) 
      self._sealed = True 
    class_w.__name__ = class_.__name__ 
    return class_w 

@singleton 
class MyClass(object): 
    def __init__(self, text): 
     print text 
    @classmethod 
    def name(class_): 
     print class_.__name__ 

x = MyClass(111) 
x.name() 
y = MyClass(222) 
print id(x) == id(y) 

de salida es:

111  # the __init__ is called only on the 1st time 
MyClass # the __name__ is preserved 
True # this is actually the same instance 

Se afirma, que si usamos el interior super(MyClass, self).__init__(text)__init__ de MyClass, de entrar en recursividad.

He probado y de hecho ocurre la recursividad. Pero, según tengo entendido, MyClass hereda object, por lo super(MyClass, self) sólo debe ser meramente object, pero resulta que super(MyClass, self) es __main__.MyClass

Podría explicar lo que sucede aquí paso a paso para mí entender las razones por las cuales la recursividad sucede ?

+1

Super() es un poco complicado con python, consulte http://rhettinger.wordpress.com/2011/05/26/super-considered-super/ – Lycha

Respuesta

5

El problema es que al escribir super(MyClass, self).__init__(text), está diciendo que utiliza el super relativo a la clase a la que se refiere MyClass en el momento en que se llama super. Pero el decorador reemplaza MyClass con una subclase de sí mismo. Entonces, cuando su método original __init__ se llama MyClass, en realidad se refiere a una subclase de la clase que define el método de ejecución.

Para decirlo paso a paso, voy a llamar a la clase original (como está escrita en la fuente) OrigMyClass, y la versión resultante (después del decorador) DecMyClass. Usaré MyClass solo como una variable, porque su significado cambia durante el curso de la ejecución.

  1. se define un método __init__ en OrigMyClass, pero que __init__ método llama super(MyClass, self), no super(OrigMyClass, self). Por lo tanto, qué método se llamará realmente depende de qué MyClass se refiere a en el momento en que se llama al método. El valor de MyClass se busca en el tiempo de ejecución como cualquier otra variable; colocarlo dentro de la llamada super o dentro del método __init__ no lo vincula mágicamente a la clase en la que se encuentra cuando lo escribe; las variables en funciones se evalúan cuando se llaman, no cuando están definidas.

  2. El decorador se ejecuta. El decorador define una nueva clase DecMyClass como una subclase de OrigMyClass. DecMyClass define un __init__ que llama al super(DecMyClass, self).

  3. Después de que se ejecuta el decorador, el nombre MyClass está vinculado a la clase DecMyClass. Tenga en cuenta que esto significa que cuando la llamada super(MyClass, self) se ejecuta más tarde, estará haciendo super(DecMyClass, self).

  4. Cuando hace MyClass(111), crea una instancia de un objeto de DecMyClass. DecMyClass.__init__ llamadas super(DecMyClass, self).__init__. Esto ejecuta OrigMyClass.__init__.

  5. OrigMyClass.__init__ llamadas super(MyClass, self).__init__.Porque MyClass se refiere a DecMyClass, esto es lo mismo que super(DecMyClass, self).__init__. Pero DecMyClass es una subclase de OrigMyClass. El punto clave es que debido a que MyClass se refiere a DecMyClass, OrigMyClass realmente llama a super en una subclase de sí mismo.

  6. Por lo tanto, super(DecMyClass, self).__init__ vuelve a llamar a OrigMyClass.__init__, que nuevamente se llama a sí mismo, y así sucesivamente hasta el infinito.

el efecto es el mismo que el código, lo que puede hacer la ruta de ejecución más evidente:

>>> class Super(object): 
...  def __init__(self): 
...   print "In super init" 
...   super(Sub, self).__init__() 
>>> class Sub(Super): 
...  def __init__(self): 
...   print "In sub init" 
...   super(Sub, self).__init__() 

Tenga en cuenta que las llamadas Supersuper(Sub, self). Está intentando llamar a un método de superclase, pero intenta llamar al método de superclase Sub. La superclase de Sub es Super, por lo que Super vuelve a llamar a su propio método.

Editar: Solo para aclarar las cuestiones nombre de consulta que ha planteado, he aquí otro ejemplo un poco diferente que tenga el mismo resultado:

>>> class Super(object): 
...  def __init__(self): 
...   print "In super init" 
...   super(someClass, self).__init__() 
>>> class Sub(Super): 
...  def __init__(self): 
...   print "In sub init" 
...   super(Sub, self).__init__() 
>>> someClass = Sub 

Esto debería dejar claro que el argumento de la clase a super (el primer argumento , aquí someClass) no es especial de ninguna manera. Es simplemente un nombre ordinario cuyo valor se busca de forma ordinaria en el momento ordinario, concretamente cuando se ejecuta la llamada super. Como se muestra en este ejemplo, la variable ni siquiera tiene que existir en el momento de definir el método; el valor se busca en el momento en que llama al método.

+0

Oh. Fue difícil de entender. ¡Pero me parece que lo tengo! La clave que no entendí fue por qué 'super (MyClass, self)' no siempre se refiere a 'OrigMyClass', pero en algún momento (después de que la decoración funciona) comienza a referirse a' DecMyClass'. Y, finalmente, obtuve que '__init__' solo busca los ámbitos ópticos hasta que encuentra' MyClass' en el alcance del módulo, donde se refiere a 'DecMyClass'. Antes de esa comprensión, pensé que '__init__' vive dentro de' MyClass', por lo que siempre debe estar vinculado a la clase inicial 'OrigMyClass'. ¡Ahora todo está claro! ¡Gracias! – ovgolovin

+0

@ovgolovin: Sí, pero no vale la pena que esto sea lo mismo que * siempre * sucede. Cualquier referencia a cualquier nombre en cualquier función siempre se busca de esta manera (a menos que se asigne el nombre). No es nada específico de 'super'. – BrenBarn

+0

@ovgolovin: He añadido otro pequeño ejemplo para aclarar esto. – BrenBarn