Cuando escribes super().método(), la intuición dice “llama al padre”. Esa intuición es incorrecta en cuanto aparece herencia múltiple. Lo que super() realmente hace es llamar al siguiente en el MRO — el Method Resolution Order — una lista lineal y determinista que Python calcula una sola vez cuando se define la clase.
El MRO no es un árbol que Python recorre en tiempo de ejecución; es una secuencia plana, calculada en tiempo de definición, almacenada en Clase.__mro__. Cada vez que buscas un atributo o método, Python recorre esa secuencia de izquierda a derecha y retorna el primero que encuentra. super() simplemente te da acceso al resto de esa secuencia a partir de la clase actual, no un acceso directo al padre declarado.
El algoritmo que construye esa secuencia se llama linearización C3 (formalizado por Kim Barrett et al., 1996, aplicado a Python en la PEP 3141). Garantiza dos propiedades que son más difíciles de satisfacer simultáneamente de lo que parecen:
- Monotonicidad: los padres siempre aparecen después de sus hijos.
- Preservación del orden local: si declaras
class C(A, B),Asiempre aparece antes queBen el MRO deC.
Esto importa porque en herencia múltiple clásica (el “problema del diamante”) una clase puede aparecer en múltiples ramas del árbol. Sin un algoritmo explícito, el orden de búsqueda sería ambiguo o inconsistente dependiendo del camino que sigas. C3 resuelve eso garantizando que el resultado sea único y predecible.
Si no entiendes el MRO, super() produce resultados sorprendentes: métodos que se saltan, métodos que se llaman dos veces, o jerarquías que Python directamente rechaza con TypeError: Cannot create a consistent method resolution order.
En Python 3, super() sin argumentos aprovecha magia del compilador (__class__ y __self__ se inyectan como cell variables) para evitar la ceremonia de Python 2 donde había que escribir super(ClaseActual, self).
# Ejemplo del problema del diamante con cooperative inheritance
class Base:
def process(self, values: list) -> list:
# Fin de la cadena: devolvemos la lista sin modificar.
# Notar que NO llamamos super().process() aquí porque
# object no tiene ese método.
return values
class Validator(Base):
def process(self, values: list) -> list:
filtered = [v for v in values if v is not None]
print(f" Validator: {len(values)} → {len(filtered)} items")
return super().process(filtered) # siguiente en el MRO, no Base directamente
class Transformer(Base):
def process(self, values: list) -> list:
transformed = [str(v).strip() for v in values]
print(f" Transformer: aplicó strip+str a {len(transformed)} items")
return super().process(transformed)
class Pipeline(Validator, Transformer):
"""
MRO calculado por C3:
Pipeline → Validator → Transformer → Base → object
Sin C3, habría ambigüedad: ¿Validator llama a Base o a Transformer?
Con C3, Validator.super() apunta a Transformer, preservando el orden
declarado en Pipeline(Validator, Transformer).
"""
def process(self, values: list) -> list:
print(f"Pipeline recibe: {values}")
result = super().process(values) # arranca la cadena del MRO
print(f"Pipeline entrega: {result}")
return result
def inspect_mro(cls) -> None:
print(f"\nMRO de {cls.__name__}:")
for i, c in enumerate(cls.__mro__):
print(f" [{i}] {c.__name__}")
if __name__ == "__main__":
inspect_mro(Pipeline)
raw = [" hola ", None, " mundo ", None, " python "]
Pipeline().process(raw)
Qué pasa en cada paso
Cuando Python evalúa class Pipeline(Validator, Transformer), C3 construye el MRO así:
L(Pipeline) = Pipeline + merge(
L(Validator), # Validator → Base → object
L(Transformer), # Transformer → Base → object
[Validator, Transformer] # orden local declarado
)
El algoritmo merge toma el primer elemento de cada lista que no aparezca en la cola de ninguna otra lista y lo añade a la secuencia final, removiéndolo de todas las listas. Esto asegura que Base no aparezca antes que Transformer, porque Base todavía está en la cola de la lista de Transformer. El resultado es Pipeline → Validator → Transformer → Base → object.
Ahora fíjate en el papel de super() dentro de Validator.process. Cuando Python llega ahí durante una llamada sobre una instancia de Pipeline, el MRO en juego es el de Pipeline, no el de Validator. Entonces super() dentro de Validator no significa “el padre de Validator” sino “el siguiente después de Validator en el MRO de la instancia concreta”. Ese siguiente es Transformer. Por eso la cadena cooperativa funciona: cada clase delega hacia adelante sin saber quién viene después, y el MRO lo orquesta todo.
Esto tiene una implicación de diseño importante: para que la herencia cooperativa funcione, todas las clases de la jerarquía deben llamar a super(), incluida la clase base (Base en el ejemplo). Si Base no llama a super().process(), la cadena se corta limpiamente en ella — lo cual está bien siempre que object no tenga ese método. Pero si una clase en medio de la jerarquía omite super(), rompe la cadena silenciosamente y las clases que vienen después en el MRO nunca se ejecutan.
La salida del ejemplo deja ver el orden con claridad:
MRO de Pipeline: [0] Pipeline [1] Validator [2] Transformer [3] Base [4] object Pipeline recibe: [' hola ', None, ' mundo ', None, ' python '] Validator: 5 → 3 items Transformer: aplicó strip+str a 3 items Pipeline entrega: ['hola', 'mundo', 'python']
Errores que debes conocer
Error: Olvidar llamar a super() en una clase intermedia, rompiendo la cadena cooperativa sin ningún aviso del intérprete.
# ❌ Wrong
class Transformer(Base):
def process(self, values: list) -> list:
transformed = [str(v).strip() for v in values]
return transformed # Base y cualquier clase posterior nunca se llaman
# ✅ Right
class Transformer(Base):
def process(self, values: list) -> list:
transformed = [str(v).strip() for v in values]
return super().process(transformed) # delega al siguiente en el MRO
Si el método termina en una clase que no sea la base de la cadena, las clases que siguen en el MRO quedan silenciosamente excluidas — exactamente el tipo de bug que tarda horas en encontrarse.
Error: Crear una jerarquía donde C3 no puede construir un MRO consistente.
# ❌ Wrong class A(Base): ... class B(Base): ... class X(A, B): ... class Y(B, A): ... class Z(X, Y): ... # TypeError: Cannot create a consistent MRO # X dice A antes que B; Y dice B antes que A. Imposible linearizar. # ✅ Right class A(Base): ... class B(Base): ... class X(A, B): ... class Y(A, B): ... # orden consistente con X class Z(X, Y): ... # MRO válido: Z → X → Y → A → B → Base → object
El error no ocurre en tiempo de ejecución al llamar el método — ocurre en el momento en que Python intenta definir la clase Z, lo que facilita detectarlo temprano.
Error: Asumir que super() en Python 3 es equivalente a nombrar la clase explícitamente cuando usas __init_subclass__ o metaclases.
# ❌ Wrong — en algunos contextos de metaclases, __class__ puede no resolver
# lo que esperas, y el resultado es recursión infinita o la clase incorrecta
class Meta(type):
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace) # aquí sí está bien
return cls
# ✅ Right — en metaclases, es preferible ser explícito cuando hay herencia de metaclases
class Meta(type):
def __new__(mcs, name, bases, namespace):
cls = type.__new__(mcs, name, bases, namespace)
return cls
super() sin argumentos funciona correctamente en métodos de instancia y de clase normales; en metaclases con herencia compleja, ser explícito elimina toda ambigüedad sobre qué super() está resolviendo.
N° 97