forma correcta de usar super (paso de argumento)

100

Así que seguí Super Considered Dañino de Python y fui a probar sus ejemplos.

Sin embargo, el Ejemplo 1-3 , que se supone que muestra la forma correcta de llamar supercuando se manejan __init__métodos que esperan diferentes argumentos, no funciona.

Esto es lo que obtengo:

~ $ python example1-3.py 
MRO: ['E', 'C', 'A', 'D', 'B', 'object']
E arg= 10
C arg= 10
A
D arg= 10
B
Traceback (most recent call last):
  File "Download/example1-3.py", line 27, in <module>
    E(arg=10)
  File "Download/example1-3.py", line 24, in __init__
    super(E, self).__init__(arg, *args, **kwargs)
  File "Download/example1-3.py", line 14, in __init__
    super(C, self).__init__(arg, *args, **kwargs)
  File "Download/example1-3.py", line 4, in __init__
    super(A, self).__init__(*args, **kwargs)
  File "Download/example1-3.py", line 19, in __init__
    super(D, self).__init__(arg, *args, **kwargs)
  File "Download/example1-3.py", line 9, in __init__
    super(B, self).__init__(*args, **kwargs)
TypeError: object.__init__() takes no parameters

Parece que en objectsí mismo viola una de las mejores prácticas mencionadas en el documento, que es que los métodos que utilicen superdeben aceptar *argsy **kwargs.

Ahora, obviamente, el Sr. Knight esperaba que sus ejemplos funcionaran, entonces, ¿es esto algo que se cambió en las versiones recientes de Python? Revisé 2.6 y 2.7, y falla en ambos.

Entonces, ¿cuál es la forma correcta de abordar este problema?

cha0site
fuente
2
Mi forma preferida es: jerarquías de herencia planas y simples.
millimoose
19
También debería leer "Python's super () consider super" para obtener una visión equilibrada :)
Björn Pollex
@ BjörnPollex: ¡Gracias! Su enlace proporciona una respuesta a la pregunta: ¿Se puede escribir una "clase raíz" que hereda de object, y se asegura a la llamada object'S __init__correctamente.
cha0site
5
Tenga __init__en cuenta que on objectignora silenciosamente cualquier parámetro en Python 2.5. Esto cambió en Python 2.6.
Wilfred Hughes
1
@Wilfred: ¡Oye, gracias por responder la pregunta real! ¡Ahora sé por qué el ensayo está desactualizado!
Cha0site

Respuestas:

105

A veces, dos clases pueden tener algunos nombres de parámetros en común. En ese caso, no puede **kwargsquitar los pares clave-valor o eliminarlos *args. En su lugar, puede definir una Baseclase que object, a diferencia de , absorbe / ignora argumentos:

class Base(object):
    def __init__(self, *args, **kwargs): pass

class A(Base):
    def __init__(self, *args, **kwargs):
        print "A"
        super(A, self).__init__(*args, **kwargs)

class B(Base):
    def __init__(self, *args, **kwargs):
        print "B"
        super(B, self).__init__(*args, **kwargs)

class C(A):
    def __init__(self, arg, *args, **kwargs):
        print "C","arg=",arg
        super(C, self).__init__(arg, *args, **kwargs)

class D(B):
    def __init__(self, arg, *args, **kwargs):
        print "D", "arg=",arg
        super(D, self).__init__(arg, *args, **kwargs)

class E(C,D):
    def __init__(self, arg, *args, **kwargs):
        print "E", "arg=",arg
        super(E, self).__init__(arg, *args, **kwargs)

print "MRO:", [x.__name__ for x in E.__mro__]
E(10)

rendimientos

MRO: ['E', 'C', 'A', 'D', 'B', 'Base', 'object']
E arg= 10
C arg= 10
A
D arg= 10
B

Tenga en cuenta que para que esto funcione, Basedebe ser la penúltima clase en el MRO.

unutbu
fuente
4
¿No deberías Basellamar super(Base, self).__init__()?
cha0site
4
Para que esto funcione, Basedebe llegar al final del MRO (excepto object). Llamar object.__init__no hace nada, por lo que está bien no llamar super(Base, self).__init__(). De hecho, creo que puede ser más claro no incluir super(Base, self).__init__()para llevar a casa el punto que Basees el final de la línea.
unutbu
26

Si va a tener mucha herencia (ese es el caso aquí), le sugiero que pase todos los parámetros usando **kwargs, y luego popinmediatamente después de usarlos (a menos que los necesite en las clases superiores).

class First(object):
    def __init__(self, *args, **kwargs):
        self.first_arg = kwargs.pop('first_arg')
        super(First, self).__init__(*args, **kwargs)

class Second(First):
    def __init__(self, *args, **kwargs):
        self.second_arg = kwargs.pop('second_arg')
        super(Second, self).__init__(*args, **kwargs)

class Third(Second):
    def __init__(self, *args, **kwargs):
        self.third_arg = kwargs.pop('third_arg')
        super(Third, self).__init__(*args, **kwargs)

Esta es la forma más sencilla de resolver ese tipo de problemas.

third = Third(first_arg=1, second_arg=2, third_arg=3)
juliomalegria
fuente
7

Como se explica en super () de Python considerado super , una forma es hacer que la clase se coma los argumentos que requiere y pase el resto. Por lo tanto, cuando la cadena de llamadas llega object, todos los argumentos se han comido y object.__init__se llamarán sin argumentos (como se espera). Entonces su código debería verse así:

class A(object):
    def __init__(self, *args, **kwargs):
        print "A"
        super(A, self).__init__(*args, **kwargs)

class B(object):
    def __init__(self, *args, **kwargs):
        print "B"
        super(B, self).__init__(*args, **kwargs)

class C(A):
    def __init__(self, arg, *args, **kwargs):
        print "C","arg=",arg
        super(C, self).__init__(*args, **kwargs)

class D(B):
    def __init__(self, arg, *args, **kwargs):
        print "D", "arg=",arg
        super(D, self).__init__(*args, **kwargs)

class E(C,D):
    def __init__(self, arg, *args, **kwargs):
        print "E", "arg=",arg
        super(E, self).__init__(*args, **kwargs)

print "MRO:", [x.__name__ for x in E.__mro__]
E(10, 20, 30)
Björn Pollex
fuente
Entonces, ¿de qué E(arg = 10)? No me gusta este método, necesito conocer el MRO de antemano para poder usarlo. El otro método en el que escribo una "clase raíz" que hereda objectparece mucho más limpio.
cha0site
Este método es útil si las funciones llamadas no comparten nombres de argumentos. Admito que no tengo mucha experiencia práctica super, por lo que este enfoque podría ser bastante teórico.
Björn Pollex