super() permite acceder a métodos de la siguiente clase en el orden de resolución de métodos. Se utiliza con frecuencia dentro de __init__ para inicializar correctamente una clase padre, pero su función es más amplia: ayuda a construir jerarquías cooperativas, reutilizar comportamiento y manejar herencia múltiple sin llamar clases concretas de forma rígida.
Antes de profundizar, conviene repasar la programación orientada a objetos, la guía específica para usar super(), los parámetros *args y **kwargs, las funciones y los decoradores.
La documentación oficial de super() explica el objeto proxy y la búsqueda de atributos. La referencia del modelo de datos ayuda a entender clases, instancias y resolución de métodos.
El problema que resuelve super()
Imagina una clase base:
class Persona:
def __init__(self, nombre):
self.nombre = nombre
def describir(self):
return f"Persona: {self.nombre}"
Una subclase necesita conservar la inicialización del nombre y añadir un atributo:
class Empleado(Persona):
def __init__(self, nombre, puesto):
super().__init__(nombre)
self.puesto = puesto
super().__init__(nombre) busca el siguiente __init__ según el MRO y lo ejecuta con la instancia actual. No necesitas pasar self explícitamente.
Por qué no llamar directamente a la clase padre
class Empleado(Persona):
def __init__(self, nombre, puesto):
Persona.__init__(self, nombre)
self.puesto = puesto
Esta forma puede funcionar en una herencia simple, pero acopla Empleado a Persona. Si cambia la jerarquía, la llamada puede quedar incorrecta. En herencia múltiple, llamar padres directamente también puede ejecutar una clase dos veces o saltarse otra.
Extender un método
class Empleado(Persona):
def __init__(self, nombre, puesto):
super().__init__(nombre)
self.puesto = puesto
def describir(self):
base = super().describir()
return f"{base}, puesto: {self.puesto}"
empleado = Empleado("Ana", "Desarrolladora")
print(empleado.describir())
La subclase reutiliza el resultado base y añade información. Esto evita duplicar el formato definido por la clase superior.
Qué es el MRO
El Method Resolution Order determina el orden de búsqueda:
print(Empleado.mro())
# [Empleado, Persona, object]
super() no significa necesariamente “mi padre directo”. Significa “continúa desde la clase actual en el MRO”. Esta diferencia es esencial en herencia múltiple.
Herencia múltiple cooperativa
class Registrable:
def __init__(self, **kwargs):
self.registros = []
super().__init__(**kwargs)
class Identificable:
def __init__(self, id_usuario, **kwargs):
self.id_usuario = id_usuario
super().__init__(**kwargs)
class Usuario(Registrable, Identificable):
def __init__(self, nombre, **kwargs):
self.nombre = nombre
super().__init__(**kwargs)
usuario = Usuario(nombre="Ana", id_usuario=10)
print(Usuario.mro())
Cada clase consume sus propios argumentos y pasa los restantes. Finalmente, object.__init__ recibe una llamada sin argumentos adicionales. Para que el patrón funcione, todas las clases participantes deben cooperar.
Diseñar inicializadores compatibles
Una estrategia es utilizar parámetros nombrados y **kwargs:
class ConFecha:
def __init__(self, fecha_creacion=None, **kwargs):
self.fecha_creacion = fecha_creacion
super().__init__(**kwargs)
No captures errores de argumentos para ocultarlos. Si queda un parámetro desconocido al final, es mejor recibir un TypeError que perder silenciosamente información.
El diamante de herencia
class A:
def accion(self):
print("A")
class B(A):
def accion(self):
print("B")
super().accion()
class C(A):
def accion(self):
print("C")
super().accion()
class D(B, C):
def accion(self):
print("D")
super().accion()
D().accion()
print(D.mro())
El resultado sigue el MRO: D, B, C y A. A se ejecuta una sola vez. Si B y C llamaran directamente a A.accion(self), A podría ejecutarse dos veces.
super() en métodos de clase
class Documento:
@classmethod
def crear(cls, datos):
return cls(datos)
class Factura(Documento):
@classmethod
def crear(cls, datos):
datos_limpios = datos.strip()
return super().crear(datos_limpios)
super() también funciona en métodos de clase porque Python conoce la clase actual y el primer argumento cls.
Uso explícito de super
La forma moderna sin argumentos es la más común dentro de una definición de clase:
super().metodo()
También existe una forma explícita:
super(Empleado, instancia).describir()
Es útil en casos avanzados, depuración o cuando se necesita iniciar la búsqueda desde un punto específico. Usarla incorrectamente puede saltarse clases, por lo que no es la primera opción.
Errores dentro de funciones anidadas
La forma de cero argumentos depende de un contexto creado por el compilador. Puede fallar dentro de una función anidada que no conserva adecuadamente ese contexto:
class Ejemplo(Persona):
def metodo(self):
def interna():
return super().describir()
return interna()
Para evitar ambigüedad, realiza la llamada en el método externo o pasa explícitamente el resultado necesario.
Cuando una clase no llama a super()
class Mezcla:
def __init__(self, **kwargs):
self.activo = True
# Falta super().__init__(**kwargs)
Si aparece en una cadena cooperativa, las clases posteriores nunca se inicializan. Este error puede manifestarse como un atributo ausente mucho después. Revisa el MRO y confirma que cada clase delega.
Evitar firmas incompatibles
Estas clases son difíciles de combinar:
class A:
def __init__(self, nombre):
super().__init__()
class B:
def __init__(self, edad):
super().__init__()
En herencia múltiple, una clase puede consumir argumentos que la siguiente necesita. El patrón con parámetros nombrados y **kwargs hace explícito el flujo, aunque debe documentarse.
Composición frente a herencia
No uses herencia solo para reutilizar dos métodos. Si una clase “tiene” un servicio en lugar de “ser” una especialización, la composición suele ser más clara:
class Repositorio:
def guardar(self, dato):
print("Guardando", dato)
class ServicioUsuarios:
def __init__(self, repositorio):
self.repositorio = repositorio
super() ayuda a gestionar una jerarquía válida, pero no convierte una jerarquía innecesaria en un buen diseño.
Mixins
Un mixin aporta un comportamiento pequeño y no suele representar una entidad completa:
class JsonMixin:
def como_dict(self):
return self.__dict__.copy()
Si un mixin define __init__, debe cooperar con super(). Mantén los mixins pequeños, sin estado cuando sea posible y con nombres que indiquen su función.
Pruebas para la jerarquía
def test_usuario_inicializa_todas_las_clases():
usuario = Usuario(nombre="Ana", id_usuario=10)
assert usuario.nombre == "Ana"
assert usuario.id_usuario == 10
assert usuario.registros == []
También puedes registrar temporalmente cada inicializador para confirmar el orden. No bases la aplicación en el orden sin documentarlo y sin pruebas.
Inspeccionar el MRO
for clase in Usuario.__mro__:
print(clase.__name__)
Hazlo cuando un método inesperado se ejecuta o cuando un atributo no se inicializa. El orden depende de cómo declaras las bases y del algoritmo C3 que Python utiliza para mantener una jerarquía consistente.
Errores frecuentes
Los problemas más comunes son olvidar super().__init__, pasar self manualmente, llamar una clase concreta dentro de una jerarquía múltiple, no reenviar argumentos, mezclar clases cooperativas con no cooperativas y asumir que super() siempre llama al padre escrito primero visualmente. También es un error construir herencia profunda cuando la composición sería más sencilla.
Reglas prácticas
Usa la forma super() sin argumentos dentro de métodos normales. Diseña firmas compatibles en jerarquías múltiples. Haz que cada clase consuma sus argumentos y delegue los restantes. Revisa mro() cuando haya dudas. Mantén la herencia poco profunda y añade pruebas que confirmen la inicialización completa.
Conclusión
super() continúa la búsqueda en el MRO y permite que varias clases colaboren sin llamadas rígidas. En herencia simple reduce acoplamiento; en herencia múltiple evita duplicaciones y saltos cuando todas las clases cooperan. Comprender el MRO, reenviar argumentos y saber cuándo elegir composición son las claves para usarlo sin errores.






