7
class A(object): 
    def __init__(self, a, b, c): 
     #super(A, self).__init__() 
     super(self.__class__, self).__init__() 


class B(A): 
    def __init__(self, b, c): 
     print super(B, self) 
     print super(self.__class__, self) 
     #super(B, self).__init__(1, b, c) 
     super(self.__class__, self).__init__(1, b, c) 

class C(B): 
    def __init__(self, c): 
     #super(C, self).__init__(2, c) 
     super(self.__class__, self).__init__(2, c) 
C(3) 

En el código anterior, la comentada __init__ llamadas aparecen al ser la forma "inteligente" comúnmente aceptado para hacer la inicialización de superclase. Sin embargo, en el caso de que la jerarquía de clases probablemente cambie, he estado usando la forma sin comentarios, hasta hace poco.invocando padres inicializador de la clase

Parece ser que en la llamada al constructor de la superclase para B en la jerarquía superior, que B.__init__ se llama de nuevo, es en realidad self.__class__C, no B como siempre había asumido.

¿Hay alguna manera en Python-2.x que puedo mantener MRO adecuada (con respecto a la inicialización de todas las clases para padres en el orden correcto) al llamar súper constructores mientras no nombrar la clase actual (el B en en super(B, self).__init__(1, b, c))?

Respuesta

3

Respuesta corta: no, no hay manera de invocar implícitamente el derecho __init__ con los argumentos adecuados de la clase padre derecha en Python 2.x

Dicho sea de paso, el código como se muestra aquí es incorrecto: si usa super(). __init__, todas las clases en su jerarquía deben tener la misma firma en sus métodos __init__. De lo contrario, su código puede dejar de funcionar si introduce una nueva subclase que usa herencia múltiple.

Consulte http://fuhm.net/super-harmful/ para obtener una descripción más larga del problema (con imágenes).

+0

empecé a preguntarme sobre las firmas init y la herencia múltiple –

1

Quizás lo que está buscando son las metaclases?

class metawrap(type): 
    def __new__(mcs,name, bases, dict): 
     dict['bases'] = bases 
     return type.__new__(mcs,name,bases,dict) 

class A(object): 
    def __init__(self): 
     pass 
    def test(self): 
     print "I am class A" 

class B(A): 
    __metaclass__ = metawrap 
    def __init__(self): 
     pass 
    def test(self): 
     par = super(self.bases[0],self) 
     par.__thisclass__.test(self) 
foo = B() 
foo.test() 

imprime "Soy la clase A"

¿Qué significa la metaclase está anulando la creación inicial de la clase B (no el objeto) y se asegura de que el diccionario incorporado para cada objeto B contiene ahora una matriz de bases donde puede encontrar todas las clases de base para B

+0

Me acabo de dar cuenta de que estoy disparando gorriones con cañones (proverbio danés), por lo que me disculpo, sin embargo, dejaré esto a menos que alguien decida eliminarlo. –

+0

preferiría no recurrir a las metaclases para algo que debería ser trivial, pero gracias a –

1

Su código no tiene nada que ver con la orden de resolución de métodos. La resolución del método se produce en el caso de la herencia múltiple, que no es el caso de su ejemplo. Su código es simplemente errónea porque se supone que self.__class__ es en realidad la misma clase de aquel en el que se define el método y esto está mal:

>>> class A(object): 
...  def __init__(self): 
...   print self.__class__ 
... 
>>> 
>>> class B(A): 
...  def __init__(self): 
...   A.__init__(self) 
... 
>>> B() 
<class '__main__.B'> 
<__main__.B object at 0x1bcfed0> 
>>> A() 
<class '__main__.A'> 
<__main__.A object at 0x1bcff90> 
>>> 

lo que cuando se debe llamar:

super(B, self).__init__(1, b, c) 

estás de hecho llamar:

# super(self.__class__, self).__init__(1, b, c) 
super(C, self).__init__(1, b, c) 

EDITAR: tratando de responder mejor a la pregunta.

class A(object): 
    def __init__(self, a): 
     for cls in self.__class__.mro(): 
      if cls is not object: 
       cls._init(self, a) 
    def _init(self, a): 
     print 'A._init' 
     self.a = a 

class B(A): 
    def _init(self, a): 
     print 'B._init' 

class C(A): 
    def _init(self, a): 
     print 'C._init' 

class D(B, C): 
    def _init(self, a): 
     print 'D._init' 


d = D(3) 
print d.a 

grabados:

D._init 
B._init 
C._init 
A._init 
3 

(Una versión modificada de template pattern).

Ahora los métodos de los padres realmente se llaman implícitamente, pero tengo que estar de acuerdo con python zen donde explícito es mejor que implícito porque el código es menos legible y la ganancia es pobre. Pero tenga en cuenta que todos los métodos _init tienen los mismos parámetros, no puede olvidar por completo a los padres y no sugiero que lo haga.

Para la herencia simple, un mejor enfoque es llamar explícitamente al método principal, sin invocar super. De este modo, no es necesario que nombre la clase actual, pero igual debe importar quién es la clase principal.

buenas lecturas son: how-does-pythons-super-do-the-right-thing y los enlaces sugieren en esa pregunta y en particularidad Python's Super is nifty, but you can't use it

Si jerarquía es probable que cambie es síntomas de un mal diseño y tiene consecuencias en todas las partes que están utilizando ese código y no debe Ser alentado.

EDIT 2

Otro ejemplo que me viene a la mente, pero que utiliza metaclases. Biblioteca Urwid uses metaclass para almacenar un atributo, __super, en la clase, de modo que solo necesita acceder a ese atributo.

Ex:

>>> class MetaSuper(type): 
...  """adding .__super""" 
...  def __init__(cls, name, bases, d): 
...   super(MetaSuper, cls).__init__(name, bases, d) 
...   if hasattr(cls, "_%s__super" % name): 
...    raise AttributeError, "Class has same name as one of its super classes" 
...   setattr(cls, "_%s__super" % name, super(cls)) 
... 
>>> class A: 
... __metaclass__ = MetaSuper 
... def __init__(self, a): 
... self.a = a 
... print 'A.__init__' 
... 
>>> class B(A): 
... def __init__(self, a): 
... print 'B.__init__' 
... self.__super.__init__(a) 
... 
>>> b = B(42) 
B.__init__ 
A.__init__ 
>>> b.a 
42 
>>> 
0

Que yo sepa, lo siguiente no se hace comúnmente. Pero parece que funciona.

Los métodos en una definición de clase determinada siempre alteran los atributos del doble guión bajo para incluir el nombre de la clase en la que están definidos. Por lo tanto, si esconde una referencia a la clase en forma desgarrada donde las instancias pueden verla , puede usar eso en la llamada al super.

Un ejemplo esconder las referencias sobre el objeto en sí mismo, mediante la implementación de __new__ en la clase base:

def mangle(cls, name): 
    if not name.startswith('__'): 
     raise ValueError('name must start with double underscore') 
    return '_%s%s' % (cls.__name__, name) 

class ClassStasher(object): 
    def __new__(cls, *args, **kwargs): 
     obj = object.__new__(cls) 
     for c in cls.mro(): 
      setattr(obj, mangle(c, '__class'), c) 
     return obj 

class A(ClassStasher): 
    def __init__(self): 
     print 'init in A', self.__class 
     super(self.__class, self).__init__() 

class B(A): 
    def __init__(self): 
     print 'init in B', self.__class 
     super(self.__class, self).__init__() 

class C(A): 
    def __init__(self): 
     print 'init in C', self.__class 
     super(self.__class, self).__init__() 

class D(B, C): 
    def __init__(self): 
     print 'init in D', self.__class 
     super(self.__class, self).__init__() 


d = D()  
print d 

Y, haciendo una cosa similar, pero usando un meta-clase y esconder los __class referencias de los objetos de la clase a sí mismos:

la ejecución de esta todos juntos, como un archivo fuente:

% python /tmp/junk.py 
init in D <class '__main__.D'> 
init in B <class '__main__.B'> 
init in C <class '__main__.C'> 
init in A <class '__main__.A'> 
<__main__.D object at 0x1004a4a50> 
init in D_meta <class '__main__.D_meta'> 
init in B_meta <class '__main__.B_meta'> 
init in C_meta <class '__main__.C_meta'> 
init in A_meta <class '__main__.A_meta'> 
<__main__.D_meta object at 0x1004a4bd0> 
Cuestiones relacionadas