Soy bastante nuevo en la programación orientada a objetos de Python y tengo problemas para entender el super()
función (nuevas clases de estilo), especialmente cuando se trata de herencia múltiple.
Por ejemplo, si tienes algo como:
class First(object):
def __init__(self):
print "first"
class Second(object):
def __init__(self):
print "second"
class Third(First, Second):
def __init__(self):
super(Third, self).__init__()
print "that's it"
Lo que no entiendo es: ¿ Third()
heredará la clase ambos métodos de constructor? En caso afirmativo, ¿cuál se ejecutará con super () y por qué?
¿Y qué pasa si quieres ejecutar el otro? Sé que tiene algo que ver con el orden de resolución del método Python ( MRO ).
python
multiple-inheritance
Calisto
fuente
fuente
super()
es útil. No recomendaría usarlo con clases que usen herencia lineal, donde es una sobrecarga inútil.super()
es que obliga a todas las subclases a usarlo también, mientras que cuando no lo usasuper()
, todos los que lo subclasifican pueden decidir por sí mismos. Si un desarrollador que lo usa no sabesuper()
o no sabe que se usó, pueden surgir problemas con el mro que son muy difíciles de rastrear.Respuestas:
Esto se detalla con una cantidad razonable de detalles por el propio Guido en su publicación de blog Método Resolución Orden (incluidos dos intentos anteriores).
En su ejemplo,
Third()
llamaráFirst.__init__
. Python busca cada atributo en los padres de la clase, ya que se enumeran de izquierda a derecha. En este caso, lo estamos buscando__init__
. Entonces, si definePython comenzará mirando
First
y, siFirst
no tiene el atributo, veráSecond
.Esta situación se vuelve más compleja cuando la herencia comienza a cruzarse (por ejemplo, si se
First
hereda deSecond
). Lea el enlace de arriba para obtener más detalles, pero, en pocas palabras, Python intentará mantener el orden en el que cada clase aparece en la lista de herencia, comenzando con la clase secundaria.Entonces, por ejemplo, si tuvieras:
el MRO sería
[Fourth, Second, Third, First].
Por cierto: si Python no puede encontrar un orden de resolución de método coherente, generará una excepción, en lugar de recurrir al comportamiento que podría sorprender al usuario.
Editado para agregar un ejemplo de un MRO ambiguo:
¿Debería
Third
ser MRO[First, Second]
o[Second, First]
? No hay expectativas obvias, y Python generará un error:Editar: veo a varias personas argumentando que los ejemplos anteriores carecen de
super()
llamadas, así que permítanme explicar: El objetivo de los ejemplos es mostrar cómo se construye el MRO. Están no destinados a imprimir "primero \ Nsecond \ tercera" o lo que sea. Puede, y debe, por supuesto, jugar con el ejemplo, agregarsuper()
llamadas, ver qué sucede y obtener una comprensión más profunda del modelo de herencia de Python. Pero mi objetivo aquí es mantenerlo simple y mostrar cómo se construye el MRO. Y está construido como lo expliqué:fuente
Su código, y las otras respuestas, tienen errores. Faltan las
super()
llamadas en las dos primeras clases que se requieren para que las subclases cooperativas funcionen.Aquí hay una versión fija del código:
La
super()
llamada encuentra el siguiente método en el MRO en cada paso, por lo que Primero y Segundo también deben tenerlo, de lo contrario, la ejecución se detiene al final deSecond.__init__()
.Esto es lo que obtengo:
fuente
super
, no se ejecutará (debido a la falta de coincidencia de parámetros), o no llamará ¡Algunas de las bases (porque no escribistesuper
en una de las bases que rompe el enlace)!Quería elaborar un poco la respuesta sin vida porque cuando comencé a leer sobre cómo usar super () en una jerarquía de herencia múltiple en Python, no lo obtuve de inmediato.
Lo que debe comprender es que
super(MyClass, self).__init__()
proporciona el siguiente__init__
método de acuerdo con el algoritmo de Orden de resolución de métodos (MRO) utilizado en el contexto de la jerarquía de herencia completa .Esta última parte es crucial de entender. Consideremos el ejemplo nuevamente:
De acuerdo con este artículo sobre la Orden de resolución de métodos de Guido van Rossum, la orden de resolución
__init__
se calcula (antes de Python 2.3) utilizando un "recorrido de izquierda a derecha de primera profundidad":Después de eliminar todos los duplicados, excepto el último, obtenemos:
Entonces, sigamos lo que sucede cuando instanciamos una instancia de la
Third
clase, por ejemplox = Third()
.Third.__init__
ejecuta.Third(): entering
super(Third, self).__init__()
ejecuta y MRO devuelve loFirst.__init__
que se llama.First.__init__
ejecutaFirst(): entering
super(First, self).__init__()
ejecuta y MRO devuelve loSecond.__init__
que se llama.Second.__init__
ejecutaSecond(): entering
super(Second, self).__init__()
ejecuta y MRO devuelve loobject.__init__
que se llama.object.__init__
se ejecuta (no hay declaraciones de impresión en el código allí)Second.__init__
que luego imprimeSecond(): exiting
First.__init__
que luego imprimeFirst(): exiting
Third.__init__
que luego imprimeThird(): exiting
Esto detalla por qué instanciar Third () resulta en:
El algoritmo MRO se ha mejorado desde Python 2.3 en adelante para que funcione bien en casos complejos, pero supongo que el uso del "recorrido de profundidad de izquierda a derecha" + "eliminando duplicados esperados para el último" todavía funciona en la mayoría de los casos (por favor comentar si este no es el caso). ¡Asegúrate de leer la publicación de blog de Guido!
fuente
Third
no heredó deSecond
, entoncessuper(First, self).__init__
llamaríaobject.__init__
y después de regresar, se imprimiría "primero". Pero porqueThird
hereda de ambosFirst
ySecond
, en lugar de llamarobject.__init__
después de queFirst.__init__
el MRO dicta que soloobject.__init__
se conserva la llamada final a , y las declaraciones de impresión enFirst
ySecond
no se alcanzan hasta queobject.__init__
regresa. ComoSecond
fue el último en llamarobject.__init__
, regresa adentroSecond
antes de regresarFirst
.List[subclass]
queList[superclass]
sisubclass
es una subclase desuperclass
(List
proviene deltyping
módulo de PEP 483 iirc).Esto se conoce como el problema del diamante , la página tiene una entrada en Python, pero en resumen, Python llamará a los métodos de la superclase de izquierda a derecha.
fuente
object
es el cuartoAsí es como resolví el problema de tener herencia múltiple con diferentes variables para la inicialización y tener MixIns múltiples con la misma llamada de función. Tuve que agregar explícitamente variables para pasar ** kwargs y agregar una interfaz MixIn para ser un punto final para las súper llamadas.
Aquí
A
hay una clase base extensibleB
yC
son clases MixIn que proporcionan funcionesf
.A
yB
ambos esperan parámetrov
en sus__init__
yC
esperaw
. La funciónf
toma un parámetroy
.Q
hereda de las tres clases.MixInF
es la interfaz mixin paraB
yC
.fuente
args
/ enkwargs
lugar de parámetros con nombre.Entiendo que esto no responde directamente a la
super()
pregunta, pero siento que es lo suficientemente relevante como para compartir.También hay una manera de llamar directamente a cada clase heredada:
Solo tenga en cuenta que si lo hace de esta manera, tendrá que llamar a cada uno manualmente, ya que estoy seguro
First
de__init__()
que no se llamará.fuente
First
ySecond
son a la vez que hereda de otra clase y decir que es directamente entonces este (punto de partida del diamante) clase común que se llama dos veces. Super está evitando esto.object
ser llamado dos veces. No pensé en eso. Solo quería aclarar que llamas directamente a las clases para padres.En general
Suponiendo que todo desciende de
object
(usted está solo si no lo hace), Python calcula un orden de resolución de método (MRO) basado en el árbol de herencia de su clase. El MRO cumple 3 propiedades:Si no existe tal orden, errores de Python. El funcionamiento interno de esto es una alineación C3 de la ascendencia de las clases. Lea todo sobre esto aquí: https://www.python.org/download/releases/2.3/mro/
Por lo tanto, en los dos ejemplos a continuación, es:
Cuando se llama a un método, la primera aparición de ese método en la MRO es la que se llama. Se omite cualquier clase que no implemente ese método. Cualquier llamada a
super
ese método llamará a la próxima aparición de ese método en el MRO. En consecuencia, importa tanto el orden en que coloca las clases en la herencia como el lugar donde coloca las llamadassuper
en los métodos.Con
super
primero en cada métodoChild()
Salidas:Con el
super
último en cada métodoChild()
Salidas:fuente
Left
usandosuper()
desdeChild
. supongamos que quiero accederRight
desde adentroChild
. ¿Hay una manera de accederRight
desdeChild
el uso de súper? ¿O debería llamar directamenteRight
desde adentrosuper
?Acerca @ comentario calfzhou de , puede utilizar, como por lo general,
**kwargs
:Ejemplo de ejecución en línea
Resultado:
También puedes usarlos posicionalmente:
pero tienes que recordar el MRO, es realmente confuso.
Puedo ser un poco molesto, pero me di cuenta de que la gente olvidaba cada vez que usaba
*args
y**kwargs
cuando anulaba un método, mientras que es uno de los pocos usos realmente útiles y sanos de estas 'variables mágicas'.fuente
Otro punto aún no cubierto es pasar parámetros para la inicialización de clases. Desde el destino de
super
depende de la subclase, la única buena manera de pasar parámetros es empacarlos todos juntos. Luego tenga cuidado de no tener el mismo nombre de parámetro con significados diferentes.Ejemplo:
da:
Llamar a la superclase
__init__
directamente a una asignación más directa de parámetros es tentador, pero falla si hay algunasuper
llamada en una superclase y / o si se cambia el MRO y se puede llamar a la clase A varias veces, dependiendo de la implementación.Para concluir: la herencia cooperativa y los parámetros súper específicos para la inicialización no funcionan muy bien juntos.
fuente
La salida es
Llamar a Third () localiza el init definido en Third. Y llamar a super en esa rutina invoca init definido en Primero. MRO = [Primero, Segundo]. Ahora llamar a super en init definido en Primero continuará buscando MRO y encontrará init definido en Segundo, y cualquier llamada a super golpeará el objeto predeterminado init . Espero que este ejemplo aclare el concepto.
Si no llamas super desde Primero. La cadena se detiene y obtendrá el siguiente resultado.
fuente
En learningpythonthehardway aprendo algo llamado super () una función incorporada si no se equivoca. Llamar a la función super () puede ayudar a que la herencia pase a través del padre y los 'hermanos' y ayudarlo a ver más claramente. Todavía soy un principiante, pero me encanta compartir mi experiencia al usar este super () en python2.7.
Si ha leído los comentarios en esta página, escuchará acerca de la Orden de resolución de método (MRO), siendo la función que escribió el método, MRO utilizará el esquema de Profundidad-Primero-Izquierda a Derecha para buscar y ejecutar. Puedes investigar más sobre eso.
Al agregar la función super ()
Puede conectar varias instancias y 'familias' con super (), agregando todas y cada una de ellas. ¡Y ejecutará los métodos, los revisará y se asegurará de que no se los pierda! Sin embargo, agregarlos antes o después hace una diferencia, usted sabrá si ha realizado el ejercicio de aprendizaje 44. ¡Que empiece la diversión!
Tomando el siguiente ejemplo, puede copiar y pegar e intentar ejecutarlo:
Como funciona La instancia de 5th () será así. Cada paso va de una clase a otra donde se agrega la superfunción.
¡Se encontró al padre e irá a Tercero y Cuarto!
¡Ahora se ha accedido a todas las clases con super ()! Se ha encontrado y ejecutado la clase padre y ahora continúa desempaquetando la función en las herencias para finalizar los códigos.
El resultado del programa anterior:
Para mí, agregar super () me permite ver más claramente cómo Python ejecutaría mi codificación y asegurarme de que la herencia pueda acceder al método que pretendía.
fuente
Me gustaría agregar a lo que @Visionscaper dice en la parte superior:
En este caso, el intérprete no filtra la clase de objeto porque está duplicada, sino porque Second aparece en una posición de cabeza y no aparece en la posición de cola en un subconjunto de jerarquía. Mientras que el objeto solo aparece en posiciones de cola y no se considera una posición fuerte en el algoritmo C3 para determinar la prioridad.
La linealización (mro) de una clase C, L (C), es el
La fusión linealizada se realiza seleccionando las clases comunes que aparecen como el encabezado de las listas y no como la cola, ya que el orden importa (se aclarará a continuación)
La linealización de Third se puede calcular de la siguiente manera:
Por lo tanto, para una implementación super () en el siguiente código:
resulta obvio cómo se resolverá este método
fuente
En python 3.5+ la herencia se ve predecible y muy agradable para mí. Por favor mira este código:
Salidas:
Como puede ver, llama a foo exactamente UNA vez por cada cadena heredada en el mismo orden en que fue heredada. Puede obtener ese pedido llamando . mro :
Cuarto -> Tercero -> Primero -> Segundo -> Base -> objeto
fuente
Tal vez todavía hay algo que se puede agregar, un pequeño ejemplo con Django rest_framework y decoradores. Esto proporciona una respuesta a la pregunta implícita: "¿por qué querría esto de todos modos?"
Como se dijo: estamos con Django rest_framework, y estamos usando vistas genéricas, y para cada tipo de objetos en nuestra base de datos nos encontramos con una clase de vista que proporciona GET y POST para listas de objetos, y otra clase de vista que proporciona GET , PUT y DELETE para objetos individuales.
Ahora, POST, PUT y DELETE que queremos decorar con el login_required de Django. Observe cómo esto toca ambas clases, pero no todos los métodos en ninguna clase.
Una solución podría pasar por herencia múltiple.
Del mismo modo para los otros métodos.
En la lista de herencia de mis clases concretas, agregaría mi
LoginToPost
antesListCreateAPIView
yLoginToPutOrDelete
antesRetrieveUpdateDestroyAPIView
. Mis clases concretasget
permanecerían sin decoración.fuente
Publicando esta respuesta para mi futura referencia.
La herencia múltiple de Python debe usar un modelo de diamante y la firma de la función no debe cambiar en el modelo.
El fragmento de código de muestra sería;
Aquí la clase A es
object
fuente
A
También debería estar llamando__init__
.A
no "inventó" el método__init__
, por lo que no puede suponer que alguna otra clase pueda haberlo hechoA
anteriormente en su MRO. La única clase cuyo__init__
método no llama (y no debería llamar)super().__init__
esobject
.object
quizás creo que debería escribir en suclass A (object) :
lugarA
no puede serobject
si está agregando un parámetro a su__init__
.