La herencia múltiple permite que una clase de Python reciba comportamiento de más de una clase base. Es una función poderosa, pero también puede crear jerarquías difíciles de entender cuando se utiliza sin una estructura clara. El objetivo no es acumular padres, sino combinar responsabilidades pequeñas sin duplicar código.
Antes de profundizar, revisa las guías sobre programación orientada a objetos, uso de super(), decoradores, dataclasses y funciones. Como referencias externas, consulta el tutorial oficial de clases y la documentación oficial de super().
Qué es la herencia múltiple
En una herencia simple, una clase deriva de un único padre. En la herencia múltiple se indican varias bases entre paréntesis:
class Volador:
def volar(self):
return "Volando"
class Nadador:
def nadar(self):
return "Nadando"
class Pato(Volador, Nadador):
pass
pato = Pato()
print(pato.volar())
print(pato.nadar())
Pato combina dos capacidades independientes. Este ejemplo funciona bien porque las clases base no compiten por el mismo método ni administran estados incompatibles.
El problema de métodos repetidos
Cuando dos padres definen un método con el mismo nombre, Python necesita decidir cuál utilizar:
class A:
def describir(self):
return "Clase A"
class B:
def describir(self):
return "Clase B"
class C(A, B):
pass
print(C().describir()) # Clase A
La clase situada primero tiene prioridad en este caso. Sin embargo, la decisión real sigue el orden de resolución de métodos, conocido como MRO.
Comprender el MRO
El MRO define el orden en que Python busca atributos y métodos. Puedes inspeccionarlo con mro():
print(C.mro())
El resultado incluye C, después A, B y finalmente object. Python utiliza una linearización consistente para evitar recorridos contradictorios. No conviene memorizar el algoritmo completo; lo importante es revisar el MRO cuando una jerarquía tenga más de dos niveles.
El problema del diamante
El llamado problema del diamante aparece cuando dos clases heredan de una base común y una cuarta clase hereda de ambas:
class Base:
def procesar(self):
print("Base")
class Izquierda(Base):
def procesar(self):
print("Izquierda")
super().procesar()
class Derecha(Base):
def procesar(self):
print("Derecha")
super().procesar()
class Final(Izquierda, Derecha):
def procesar(self):
print("Final")
super().procesar()
Final().procesar()
La salida recorre cada implementación una sola vez porque todas cooperan con super(). Llamar directamente a Base.procesar(self) desde varios padres podría ejecutar la base más de una vez.
super() no significa simplemente “el padre”
En herencia múltiple, super() significa “continúa con la siguiente clase del MRO”. Por eso cada clase cooperativa debe llamar a super() y aceptar una interfaz compatible.
class Identificado:
def __init__(self, *, identificador, **kwargs):
self.identificador = identificador
super().__init__(**kwargs)
class Registrado:
def __init__(self, *, usuario, **kwargs):
self.usuario = usuario
super().__init__(**kwargs)
class Recurso(Identificado, Registrado):
def __init__(self, **kwargs):
super().__init__(**kwargs)
recurso = Recurso(identificador=10, usuario="ana")
Los argumentos nombrados y **kwargs permiten que cada clase consuma únicamente los datos que le pertenecen y envíe el resto a la siguiente.
Mixins
Un mixin es una clase pequeña que añade una capacidad específica, pero no pretende representar una entidad completa. Normalmente no controla el ciclo de vida principal del objeto.
import json
class JsonMixin:
def to_json(self):
return json.dumps(self.__dict__, ensure_ascii=False)
class LogMixin:
def registrar(self, mensaje):
print(f"[{self.__class__.__name__}] {mensaje}")
class Usuario(JsonMixin, LogMixin):
def __init__(self, nombre):
self.nombre = nombre
Los nombres terminados en Mixin ayudan a comunicar que esa clase ofrece comportamiento reutilizable.
Cuándo usar composición
La composición suele ser mejor cuando la relación es “tiene un” en lugar de “es un”. Un servicio de informes puede tener un exportador y un repositorio, sin heredar de ellos:
class ServicioInformes:
def __init__(self, repositorio, exportador):
self.repositorio = repositorio
self.exportador = exportador
La composición reduce el acoplamiento y permite sustituir dependencias con facilidad durante las pruebas.
Diseñar interfaces compatibles
Las clases que participan en una cadena cooperativa deben usar nombres de parámetros compatibles y evitar asumir que son las últimas. También deben llamar a super() incluso cuando parezca innecesario, porque otra clase puede incorporarse después.
Evitar estados duplicados
No permitas que varios padres creen atributos con el mismo nombre para significados distintos. Un atributo estado utilizado por dos clases puede producir errores difíciles de rastrear. Prefiere nombres explícitos como estado_conexion y estado_documento.
Pruebas del MRO
Cuando el orden sea importante, escríbelo como una expectativa:
def test_mro_recurso():
nombres = [clase.__name__ for clase in Recurso.mro()]
assert nombres[:3] == ["Recurso", "Identificado", "Registrado"]
También prueba que cada inicializador se ejecute una sola vez y que todos los atributos esperados existan.
Errores frecuentes
Los problemas habituales son llamar directamente a un padre, mezclar clases cooperativas con clases que no llaman a super(), repetir nombres de atributos, crear demasiados niveles y utilizar mixins que contienen demasiada lógica. Otro error es cambiar el orden de las bases sin revisar cómo cambia el MRO.
Reglas prácticas
Mantén las clases base pequeñas, usa mixins para capacidades concretas, inspecciona mro(), utiliza argumentos nombrados en inicializadores cooperativos y documenta el contrato de cada clase. Cuando la jerarquía resulte difícil de explicar en pocas frases, probablemente convenga reemplazar parte de ella por composición.
Conclusión
La herencia múltiple funciona bien cuando combina responsabilidades independientes y todas las clases respetan el MRO. super() debe utilizarse como una cadena cooperativa, no como una llamada rígida a un padre específico. Los mixins pequeños, las interfaces compatibles y las pruebas del orden de resolución permiten aprovechar esta característica sin crear jerarquías frágiles.





