curso
Introducción a la herencia múltiple y super()
Visión rápida de la herencia
A medida que hagas crecer tus proyectos y paquetes Python, inevitablemente querrás utilizar clases y aplicar el principio DRY (don't-repeat-yourself) mientras lo haces. La herencia de clases es una forma fantástica de crear una clase basada en otra clase para permanecer DRY. En este post se tratarán conceptos más avanzados de la herencia, y no se profundizará en la herencia básica. Haremos una introducción rápida, pero hay introducciones mucho mejores y detalladas por ahí. Aquí tienes algunos recursos para empezar: Curso de Programación Orientada a Objetos en Python & Programación Orientada a Objetos en Python (POO): Tutorial.
¿Qué es la herencia de clases? De forma similar a la genética, una clase hija puede "heredar" atributos y métodos de una clase padre. Veamos un ejemplo de código. En el siguiente bloque de código demostraremos la herencia con una clase Child
que hereda de una clase Parent
.
Entrada
class Parent:
def __init__(self):
self.parent_attribute = 'I am a parent'
def parent_method(self):
print('Back in my day...')
# Create a child class that inherits from Parent
class Child(Parent):
def __init__(self):
Parent.__init__(self)
self.child_attribute = 'I am a child'
# Create instance of child
child = Child()
# Show attributes and methods of child class
print(child.child_attribute)
print(child.parent_attribute)
child.parent_method()
Salida
I am a child
I am a parent
Back in my day...
Vemos que la clase Child
"hereda" atributos y métodos de la clase Parent
. Sin ningún trabajo por nuestra parte, el Parent.parent_method
forma parte de la clase Child
. Para obtener los beneficios del método Parent.__init__()
necesitábamos llamar explícitamente al método y pasar self
. Esto se debe a que cuando añadimos un método __init__
a Child
, sobrescribimos el heredado __init__
.
Una vez aclarado este breve y poco exhaustivo resumen, pasemos al meollo de la cuestión.
Introducción a super
En el caso más sencillo, se puede utilizar la función super
para sustituir la llamada explícita a Parent.__init__(self)
. Nuestro ejemplo de introducción de la primera sección puede reescribirse con super
como se ve a continuación. Ten en cuenta que el siguiente bloque de código está escrito en Python 3, las versiones anteriores utilizan una sintaxis ligeramente diferente. Además, se ha omitido la salida, ya que es idéntica al primer bloque de código.
class Parent:
def __init__(self):
self.parent_attribute = 'I am a parent'
def parent_method(self):
print('Back in my day...')
# Create a child class that inherits from Parent
class Child(Parent):
def __init__(self):
super().__init__()
self.child_attribute = 'I am a parent'
# Create instance of child
child = Child()
# Show attributes and methods of child class
print(child.child_attribute)
print(child.parent_attribute)
child.parent_method()
Para ser sincero, super
en este caso nos da poca ventaja, si es que nos da alguna. Dependiendo del nombre de nuestra clase padre podríamos ahorrarnos algunas pulsaciones, y no tenemos que pasar self
a la llamada a __init__
. A continuación se exponen algunos pros y contras del uso de super
en casos de herencia única.
Contras
Se puede argumentar que utilizar super
aquí hace que el código sea menos explícito. Hacer el código menos explícito viola El Zen de Python, que afirma: "Lo explícito es mejor que lo implícito".
Pros
Se puede argumentar a favor de la mantenibilidad de super
incluso en la herencia simple. Si por cualquier motivo tu clase hija cambia su patrón de herencia (es decir, cambia la clase padre o se pasa a la herencia múltiple), no es necesario encontrar y reemplazar todas las referencias persistentes a ParentClass.method_name()
; el uso de super
permitirá que todos los cambios fluyan con el cambio en la declaración class
.
Domina tus habilidades de datos con DataCamp
Más de 10 millones de personas aprenden Python, R, SQL y otras habilidades tecnológicas con nuestros cursos prácticos elaborados por expertos del sector.

super
y herencia múltiple
Antes de entrar en la herencia múltiple y super
... Atención, esto puede volverse bastante raro y complicado.
En primer lugar, ¿qué es la herencia múltiple? Hasta ahora, el código de ejemplo ha abarcado una única clase hija que hereda de una única clase padre. En la herencia múltiple, hay más de una clase padre. Una clase hija puede heredar de 2, 3, 10, etc. clases padre.
Aquí es donde las ventajas de super
se hacen más evidentes. Además de ahorrarte las pulsaciones de teclas de referenciar los distintos nombres de las clases padre, utilizar super
con múltiples patrones de herencia tiene ventajas matizadas. En resumen, si vas a utilizar la herencia múltiple, utiliza super
.
Herencia múltiple sin super
Veamos un ejemplo de herencia múltiple que evita modificar cualquier método padre y, a su vez, evita super
.
Entrada
class B:
def b(self):
print('b')
class C:
def c(self):
print('c')
class D(B, C):
def d(self):
print('d')
d = D()
d.b()
d.c()
d.d()
Salida
b
c
d
Orden de resolución múltiple
Este resultado no es demasiado sorprendente, dado el concepto de herencia múltiple. D
heredó los métodos x
y z
de sus clases padre, y todo va bien en el mundo... por ahora.
¿Y si tanto B
como C
tuvieran un método con el mismo nombre? Aquí es donde entra en juego un concepto llamado "orden de resolución múltiple" o MRO, por sus siglas en inglés. La MRO de una clase hija es lo que decide dónde buscará Python un método determinado, y a qué método llamará cuando haya un conflicto.
Veamos un ejemplo.
Entrada
class B:
def x(self):
print('x: B')
class C:
def x(self):
print('x: C')
class D(B, C):
pass
d = D()
d.x()
print(D.mro())
Salida
x: B
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class 'object'>]
Cuando llamamos al método heredado x
, sólo vemos la salida heredada de B
. Podemos ver el MRO de nuestra clase D
llamando al método de la clase mro
. De la salida de D.mro()
aprendemos lo siguiente: nuestro programa intentará llamar a los métodos de D
por defecto, luego recurrirá a B
, después a C
, y finalmente a object
. Si no se encuentra en ninguno de esos lugares, obtendremos el error de que D
no tiene el método que pedimos.
Cabe señalar que, por defecto, todas las clases heredan de object
, y está en la cola de todas las MRO.
Herencia múltiple, super
y el problema del diamante
A continuación se muestra un ejemplo de uso de super para gestionar MRO de init de forma beneficiosa. En el ejemplo, creamos una serie de clases de tratamiento de texto y combinamos su funcionalidad en otra clase con herencia múltiple. Crearemos 4 clases, y la estructura para la herencia seguirá la estructura del diagrama siguiente.
Nota: Esta estructura tiene fines ilustrativos y, salvo restricciones, habría mejores formas de aplicar esta lógica.
Esto es en realidad un ejemplo del "problema del diamante" de la herencia múltiple. Su nombre se basa, por supuesto, en la forma de su diseño, y en el hecho de que es un problema bastante confuso.
A continuación se escribe el diseño utilizando super
.
Entrada
class Tokenizer:
"""Tokenize text"""
def __init__(self, text):
print('Start Tokenizer.__init__()')
self.tokens = text.split()
print('End Tokenizer.__init__()')
class WordCounter(Tokenizer):
"""Count words in text"""
def __init__(self, text):
print('Start WordCounter.__init__()')
super().__init__(text)
self.word_count = len(self.tokens)
print('End WordCounter.__init__()')
class Vocabulary(Tokenizer):
"""Find unique words in text"""
def __init__(self, text):
print('Start init Vocabulary.__init__()')
super().__init__(text)
self.vocab = set(self.tokens)
print('End init Vocabulary.__init__()')
class TextDescriber(WordCounter, Vocabulary):
"""Describe text with multiple metrics"""
def __init__(self, text):
print('Start init TextDescriber.__init__()')
super().__init__(text)
print('End init TextDescriber.__init__()')
td = TextDescriber('row row row your boat')
print('--------')
print(td.tokens)
print(td.vocab)
print(td.word_count)
Salida
Start init TextDescriber.__init__()
Start WordCounter.__init__()
Start init Vocabulary.__init__()
Start Tokenizer.__init__()
End Tokenizer.__init__()
End init Vocabulary.__init__()
End WordCounter.__init__()
End init TextDescriber.__init__()
--------
['row', 'row', 'row', 'your', 'boat']
{'boat', 'your', 'row'}
5
En primer lugar, vemos que la clase TextDescriber
ha heredado todos los atributos del árbol genealógico de clases. Gracias a la herencia múltiple podemos "combinar" la funcionalidad de más de una clase.
Hablemos ahora de las impresiones procedentes de los métodos init
de la clase:
Cada __init__
fue llamado una y sólo una vez.
La clase TextDescriber
hereda de 2 clases que heredan de Tokenizer
. ¿Por qué no se llamó dos veces a Tokenizer.__init__
?
Si sustituyéramos todas nuestras llamadas a super
por el método antiguo, acabaríamos teniendo 2 llamadas a Tokenizer.__init__
. Las llamadas a super
"piensan" un poco más en nuestro patrón y se saltan el viaje extra a A
.
Cada __init__
se inició antes de que se terminaran los demás.
Merece la pena tener en cuenta el orden de inicio y fin de cada __init__
por si intentas establecer un atributo que tiene un conflicto de nombres con otra clase padre. El atributo se sobrescribirá, y puede resultar muy confuso.
En nuestro caso, hemos evitado los conflictos de nombres con los atributos heredados, así que todo funciona como se esperaba.
Para reiterar, el problema del diamante puede complicarse rápidamente y conducir a resultados inesperados. En la mayoría de los casos de programación, es mejor evitar los diseños complicados.
Lo que hemos aprendido
- Hemos aprendido sobre la función
super
y cómo puede utilizarse para sustituir aParentName.method
en la herencia simple. Ésta puede ser una práctica más fácil de mantener. - Hemos aprendido sobre la herencia múltiple y cómo podemos pasar la funcionalidad de varias clases padre a una única clase hijo.
- Hemos aprendido sobre el orden de resolución múltiple y cómo decide lo que ocurre en la herencia múltiple cuando hay un conflicto de nombres entre métodos padre.
- Aprendimos sobre el problema del diamante y vimos un ejemplo de cómo el uso de
super
navega por el diamante.
Más información sobre Python
curso
Python intermedio
curso