2012-02-20 28 views
8

Déjame empezar con esto no es una repetición de Why does __init__ not get called if __new__ called with no args. He intentado construir cuidadosamente un código de muestra para __new__ y __init__ que no tenga ninguna explicación que pueda encontrar.¿Por qué __init__ no se llama después __new__ A VECES

Los parámetros básicos:

  • Hay una clase base llamada NotMine ya que proviene de otra biblioteca (Voy a revelar al final, no es importante aquí)
  • Esa clase tiene un método que __init__ a su vez llama a un método _parse
  • tengo que reemplazar el método _parse en las subclases
  • qué subclase no se conoce hasta la invocación estoy creando
  • Sé que hay métodos de diseño de fábrica, pero no puedo usarlos aquí (más al final)
  • He tratado de hacer un uso cuidadoso de super para evitar los problemas en Python logging: Why is __init__ called twice?
  • Sé que esto es también 'tipo de 'una oportunidad AbstractBaseMehtod pero eso no ayudó

de todos modos, __init__ debe ser llamado después de __new__ y por cada explicación de por qué algunas muestras de abajo no funcionan parezco ser capaz de señalar a otros casos que hacen el trabajo y descarta la explicación

class NotMine(object): 

    def __init__(self, *args, **kwargs): 
     print "NotMine __init__" 
     self._parse() 

    def _parse(self): 
     print "NotMine _parse" 

class ABC(NotMine): 
    def __new__(cls,name,*args, **kwargs): 
     print "-"*80 
     print "Entered through the front door ABC.__new__(%s,%s,*%s,**%s)"%(cls,name,args,kwargs) 
     if name == 'AA': 
      obj = super(NotMine,ABC).__new__(AA,*args,**kwargs) 
      print "Exiting door number 1 with an instance of: %s"%type(obj) 
      return obj 
     elif name == 'BB': 
      obj = super(NotMine,ABC).__new__(BB,*args,**kwargs) 
      print "Exiting door number 2 with an instance of: %s"%type(obj) 
      return obj 
     else: 
      obj = super(NotMine,ABC).__new__(cls,*args,**kwargs) 
      print "Exiting door number 3 with an instance of: %s"%type(obj) 
      return obj 

class AA(ABC): 

    def _parse(self): 
     print "AA _parse" 

class BB(ABC): 

    def __init__(self, *args, **kw): 
     print "BB_init:*%s, **%s"%(args,kw)   
     super(BB,self).__init__(self,*args,**kw) 

    def _parse(self): 
     print "BB _parse" 

class CCC(AA): 

    def _parse(self): 
     print "CCCC _parse" 


print("########### Starting with ABC always calls __init__ ############") 
ABC("AA")   # case 1 
ABC("BB")   # case 2 
ABC("NOT_AA_OR_BB") # case 3 

print("########### These also all call __init__ ############") 
AA("AA")   # case 4 
BB("BB")   # case 5 
AA("NOT_AA_OR_BB") # case 6 
BB("NOT_AA_OR_BB") # case 7 
CCC("ANYTHING") # case 8 

print("########### WHY DO THESE NOT CALL __init__ ############") 
AA("BB") # case 9 
BB("AA") # case 10 
CCC("BB") # case 11 

Si se ejecuta el código, se puede ver que para cada llamada a __new__ que anuncia "qué puerta" está saliendo a través y con qué tipo. Puedo salir de la misma "puerta" con el mismo objeto "tipo" y llamar al __init__ en un caso y no en el otro. Miré el mro de la clase "llamar" y eso no ofrece ninguna información ya que puedo invocar esa clase (o un subcampo como en CCC) y tengo __init__ llamado.

Fin Notas: El NotMine biblioteca que estoy usando es el Genshi MarkupTemplate y la razón para no usar un método de diseño de fábrica es que su TemplateLoader necesita un defaultClass para construir. No lo sé hasta que empiece a analizar, lo que hago en __new__. Hay una gran cantidad de magia vudú que los cargadores genshi y las plantillas hacen que valga la pena el esfuerzo.

Puedo ejecutar una instancia no modificada de su cargador y actualmente todo funciona, siempre y cuando SÓLO pase la clase ABC (resumen de la clase de fábrica) como valor predeterminado. Las cosas funcionan bien, pero este comportamiento inexplicable es un error casi seguro más adelante.

ACTUALIZACIÓN: Ignacio, clavó la pregunta línea superior, si el objeto devuelto no es una "instancia de" cls __init__ entonces no se llama. Encuentro que llamar al "constructor" (por ejemplo, AA(args..) es incorrecto ya que llamará nuevamente al __new__ y ya está donde comenzó. Podría modificar un arg para tomar una ruta diferente. Eso solo significa que llama al ABC.__new__ dos veces en lugar de infinitamente .Una solución de trabajo es editar class ABC anterior como:

class ABC(NotMine): 
    def __new__(cls,name,*args, **kwargs): 
    print "-"*80 
    print "Entered through the front door ABC.__new__(%s,%s,*%s,**%s)"%(cls,name,args,kwargs) 
    if name == 'AA': 
     obj = super(NotMine,ABC).__new__(AA,*args,**kwargs) 
     print "Exiting door number 1 with an instance of: %s"%type(obj) 
    elif name == 'BB': 
     obj = super(NotMine,ABC).__new__(BB,*args,**kwargs) 
     print "Exiting door number 2 with an instance of: %s"%type(obj) 
    elif name == 'CCC': 
     obj = super(NotMine,ABC).__new__(CCC,*args,**kwargs) 
     print "Exiting door number 3 with an instance of: %s"%type(obj) 
    else: 
     obj = super(NotMine,ABC).__new__(cls,*args,**kwargs) 
     print "Exiting door number 4 with an instance of: %s"%type(obj) 
    ## Addition to decide who calls __init__ ## 
    if isinstance(obj,cls): 
     print "this IS an instance of %s So call your own dam __init__"%cls 
     return obj 
    print "this is NOT an instance of %s So __new__ will call __init__ for you"%cls 
    obj.__init__(name,*args, **kwargs) 
    return obj 

print("########### now, these DO CALL __init__ ############") 
AA("BB") # case 9 
BB("AA") # case 10 
CCC("BB") # case 11 

Aviso las últimas líneas. No llamar al __init__ si es una clase "diferente" no tiene sentido para mí, ESPECIALMENTE cuando la clase "diferente" todavía es una subclase de la clase que llama al __init__. No me gustan las ediciones anteriores, pero ahora obtengo las reglas un poco mejor.

+0

¿Genshi usa una metaclase? Ver http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python – Borealid

+0

No, Mi código de muestra no usa el genshi como base. –

Respuesta

7

De the documentation:

Si __new__() no devuelve una instancia de cls, entonces no se invocará el método de la nueva instancia __init__().

Esto es para permitir a __new__()return a new instance of a different class, que tiene su propio __init__() ser llamado en su lugar. Tendrá que detectar si está creando un nuevo cls y, si no, llamar al constructor apropiado.

+0

OK, me da el súper literal "no devuelve una instancia de cls". Pero, estoy devolviendo una nueva instancia de una clase diferente, que tiene su propio '__init__' pero no se llama, incluso si es una base común (en el viejo mro). ¿** Se supone que debo llamarlo nuevo? –

+0

Se supone que debes * crear una instancia * en '__new__()', llamando a su constructor. –

+0

ouch. Mala idea aqui. Pensé que esto podría llevar al problema "llamado dos veces" que cité arriba, pero dado que ABC es una base común, llamar al constructor en el código causa una llamada recursiva a '__new__' y bloquea la sesión :-( –

0

Solo tengo dos centavos aquí, pero ¿por qué no utilizas el tipado de pato Python para proporcionar a Genshi algo que se comporte como una clase?

Eché un vistazo rápido a Genshi source code y los únicos requisitos que vi en el parámetro 'clase' para el TemplateLoader es que se puede llamar con los argumentos dados.

Creo que sería más fácil burlarse de una clase con una función de fábrica devolviendo la instancia creada real.

+0

sí, en el fondo de la fuente que eventualmente llama al constructor en la clase_predeterminada que podría ser cualquier llamable. Creo que esta sería una alternativa viable. El futuro podría ser diferente y "parece" que pasar una subclase de MarkupTemplate es lo correcto de OO. Pero otra vez me "pareció" que '__new__' debería haberse comportado de manera diferente, entonces ¿quién soy yo para hablar ;-) –

Cuestiones relacionadas