Herencia y MRO
Python soporta herencia múltiple y resuelve métodos con el Orden de Resolución de Métodos (MRO). super() recorre ese orden, esencial para cadenas cooperativas de __init__ en mixins.
Receta
class LogMixin:
def log(self, msg: str) -> None:
print(f"[{self.__class__.__name__}] {msg}")
class Service(LogMixin):
def run(self) -> None:
self.log("starting")Cuándo usar esto:
- Compartir comportamiento transversal a través de mixins (registro, serialización)
- Extender clases base de frameworks (modelos de Django, ABCs)
- Depurar sobrescrituras de métodos inesperadas en jerarquías
- Diseñar APIs de plugins con el patrón de método de plantilla
Ejemplo Funcional
class Base:
def __init__(self) -> None:
self.base_init = True
class TrackingMixin:
def __init__(self, *args, **kwargs) -> None:
self.tracked = True
super().__init__(*args, **kwargs)
class TimestampMixin:
def __init__(self, *args, **kwargs) -> None:
self.timestamped = True
super().__init__(*args, **kwargs)
class Event(TrackingMixin, TimestampMixin, Base):
pass
if __name__ == "__main__":
e = Event()
print(e.tracked, e.timestamped, e.base_init)
print(Event.mro())Lo que esto demuestra:
- Las llamadas cooperativas a
super().__init__ejecutan cada clase en el MRO una vez - Los Mixins anteponen comportamiento sin árboles profundos
Event.mro()muestra el orden de resolución para depuración- Todos los
__init__de los mixins se ejecutan antes de queBasecomplete la cadena
Profundización
Cómo Funciona
- Linealización C3 - El MRO respeta la precedencia local y la monotonicidad.
- Proxy
super()- Salta a la siguiente clase en el MRO, no necesariamente al padre directo. - Sobrescritura de métodos - El método de la subclase oculta al de la base;
super()llama a la siguiente implementación. - Problema del diamante - C3 asegura que cada clase aparezca una vez en un orden sensato.
- Encadenamiento de
__init__- Cada clase en el patrón cooperativo debe llamar asuper().__init__.
Inspeccionar MRO
HelpfulClass.mro() # lista de clases en orden de búsqueda
HelpfulClass.__mro__ # forma de tuplaNotas de Python
# llamada explícita de sobrescritura
class Child(Parent):
def save(self) -> None:
super().save()
self.post_save_hook()Errores Comunes
super()faltante en mixin - Rompe la cadena; algunas bases nunca se inicializan. Solución: Siempre usarsuper().__init__(*args, **kwargs)en mixins.- Torres de herencia profundas - Sobrescrituras frágiles. Solución: Composición o diseño basado en protocolos.
- Atributos de clase mutables heredados - Una lista compartida en la subclase muta la vista del padre. Solución: Establecer por instancia en
__init__. - Mezcla de bases no relacionadas - MRO y superficie de API confusas. Solución: Mixins pequeños con una sola responsabilidad.
super()sin bases cooperativas - Clases heredadas que no llaman asuper()rompen el patrón. Solución: Adaptar o evitar la herencia múltiple.
Alternativas
| Alternativa | Usar Cuándo | No Usar Cuándo |
|---|---|---|
| Composición | Relaciones "tiene-un" | Polimorfismo "es-un" verdadero |
| Tipado de protocolos | Interfaces estructurales | Necesidad de implementación compartida |
| Funciones + despacho | Reutilización de comportamiento simple | El patrón de método de plantilla encaja |
| Herencia simple | Jerarquía clara | Solo se necesita una capa de mixin |
Preguntas Frecuentes
¿Qué es MRO?
El orden en que Python busca bases para atributos/métodos. Imprimir con Class.mro().
¿Por qué `super()` y no `Parent.method()`?
super() respeta el MRO para herencia múltiple; el padre codificado ignora los mixins cooperativos.
¿Cuántos mixins?
Pocos y pequeños están bien. Muchos mixins señalan un mal diseño; considera la composición.
¿Puedo heredar de dos clases no relacionadas?
Técnicamente sí; ten cuidado con la claridad de la API y las sorpresas del MRO. A menudo, la composición es más clara.
¿Clase base abstracta en la jerarquía?
Una ABC puede estar en el MRO, exigiendo métodos antes que las clases concretas.
¿Reglas para sobrescribir `__init__`?
Llama a super().__init__ a menos que reemplaces deliberadamente toda la cadena; documenta el porqué.
¿Atributo de resolución de métodos?
El atributo de instancia oculta el método de clase; los descriptores y descriptores de datos añaden matices.
Herencia de `__slots__`?
Los slots del hijo deben incluir una declaración vacía de los slots del padre; es complejo; lee la documentación antes de combinar.
¿Probar el MRO?
Asegura Class.mro()[1] == ExpectedParent cuando el orden del mixin es crítico.
¿Herencia en frameworks?
Los patrones de Django/FastAPI a menudo heredan de clases de framework; sigue la documentación del framework para las llamadas a super.
Relacionados
- Clases Base Abstractas - ABCs en jerarquías
- Protocolos y Tipado Estructural - alternativa a la herencia
- Métodos de Clase vs. Instancia vs. Estáticos -
super()con métodos de clase - Mejores Prácticas de Python Orientado a Objetos - guía de composición
Versiones de la pila: Esta página fue escrita para Python 3.14.0 (estable 3.14, mantenimiento 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, y uv 0.6+.