CAP 13 · LEC 04·Conceptos profundos de Python

MRO y herencia múltiple: el algoritmo C3 de Python

Python permite herencia de múltiples clases. El algoritmo C3 (Method Resolution Order) define el orden en que se buscan los métodos — evitando el problema del diamante que plaga a otros lenguajes.

● AVANZADO10 min lectura3 ejerciciospor Fernando Herrera · actualizado mayo de 2026
¿Encontraste un error o algo que mejorar?Editá esta lección en GitHub →

El problema del diamante

El problema del diamante ocurre cuando una clase hereda de dos clases que a su vez heredan de una misma clase base. ¿Qué método se usa? ¿Cuál base se inicializa primero? Python lo resuelve con el algoritmo C3.

# Jerarquía en diamante: # Animal # / # Volador Nadador # / # Pato class Animal: def __init__(self): print("Animal.__init__") def respirar(self): return "respirando..." class Volador(Animal): def __init__(self): print("Volador.__init__") super().__init__() # super() sigue el MRO, no la herencia directa def mover(self): return "volando" class Nadador(Animal): def __init__(self): print("Nadador.__init__") super().__init__() def mover(self): return "nadando" class Pato(Volador, Nadador): def __init__(self): print("Pato.__init__") super().__init__() # inicia toda la cadena MRO pato = Pato() # Pato.__init__ # Volador.__init__ # Nadador.__init__ # Animal.__init__ # Animal.__init__ se llama UNA SOLA VEZ — C3 garantiza esto print(pato.mover()) # "volando" — Volador está primero en el MRO print(pato.respirar()) # "respirando..." — de Animal
SalidaPato.__init__ Volador.__init__ Nadador.__init__ Animal.__init__ volando respirando...

El algoritmo C3 explicado

C3 linearization calcula el MRO respetando dos reglas: los hijos van antes que los padres, y el orden de las bases se preserva. El resultado es una lista lineal y sin ambigüedad.

# El MRO de Pato es: [Pato, Volador, Nadador, Animal, object] # C3 resuelve esto preservando el orden local de cada clase class A: def metodo(self): return "A" class B(A): def metodo(self): return "B → " + super().metodo() class C(A): def metodo(self): return "C → " + super().metodo() class D(B, C): def metodo(self): return "D → " + super().metodo() print(D().metodo()) # D → B → C → A print(D.__mro__) # (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>) # C3 rechaza jerarquías inconsistentes try: class X: pass class Y(X): pass # Inconsistente: X antes de Y en primera base, Y antes de X en segunda class Z(X, Y): pass # TypeError: Cannot create a consistent MRO except TypeError as e: print(f"MRO inconsistente: {e}") # Reglas del C3: # 1. La clase siempre va antes que sus bases # 2. El orden de las bases declaradas se preserva # 3. Si una clase aparece en múltiples listas, solo pasa cuando # ya no aparece en la cabeza de ninguna otra lista
SalidaD → B → C → A (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>) MRO inconsistente: Cannot create a consistent method resolution order...

__mro__ y mro() para inspeccionar

Cada clase expone su MRO a través del atributo __mro__ (una tupla) y el método mro() (una lista). También se puede inspeccionar con help(MiClase).

class Base: pass class Mixin1(Base): def metodo1(self): return "mixin1" class Mixin2(Base): def metodo2(self): return "mixin2" class Final(Mixin1, Mixin2): pass # __mro__ retorna una tupla print(Final.__mro__) # (<class 'Final'>, <class 'Mixin1'>, <class 'Mixin2'>, <class 'Base'>, <class 'object'>) # mro() retorna una lista for cls in Final.mro(): print(f" {cls.__name__}") # Final → Mixin1 → Mixin2 → Base → object # Herramienta de diagnóstico: qué clase provee cada método def method_source(cls, method_name: str) -> str: for klass in cls.__mro__: if method_name in klass.__dict__: return klass.__name__ return "no encontrado" print(method_source(Final, "metodo1")) # Mixin1 print(method_source(Final, "metodo2")) # Mixin2 print(method_source(Final, "__init__")) # object # Inspeccionar el MRO de clases built-in print([c.__name__ for c in int.__mro__]) # ['int', 'object'] print([c.__name__ for c in bool.__mro__]) # ['bool', 'int', 'object']
Salida(<class '__main__.Final'>, <class '__main__.Mixin1'>, <class '__main__.Mixin2'>, <class '__main__.Base'>, <class 'object'>) Final Mixin1 Mixin2 Base object Mixin1 Mixin2 object ['int', 'object'] ['bool', 'int', 'object']

Mixin pattern con herencia múltiple

Los mixins son clases pequeñas que añaden funcionalidad específica a otras clases mediante herencia múltiple. No deben instanciarse solos y no tienen estado propio — solo proveen métodos.

import json import time import functools # Mixin de serialización JSON class JSONMixin: def to_json(self) -> str: return json.dumps(self.__dict__, ensure_ascii=False, default=str) @classmethod def from_json(cls, json_str: str): data = json.loads(json_str) obj = cls.__new__(cls) obj.__dict__.update(data) return obj # Mixin de representación class ReprMixin: def __repr__(self) -> str: attrs = ", ".join(f"{k}={v!r}" for k, v in self.__dict__.items()) return f"{self.__class__.__name__}({attrs})" # Mixin de validación class ValidatedMixin: def validate(self) -> bool: for attr, value in self.__dict__.items(): if value is None: raise ValueError(f"'{attr}' no puede ser None") return True # Clase final: hereda de todos los mixins + base propia class Product(JSONMixin, ReprMixin, ValidatedMixin): def __init__(self, name: str, price: float, stock: int): self.name = name self.price = price self.stock = stock p = Product("Python Handbook", 29.99, 50) print(repr(p)) # Product(name='Python Handbook', price=29.99, stock=50) print(p.to_json()) # {"name": "Python Handbook", "price": 29.99, "stock": 50} print(p.validate()) # True p2 = Product.from_json('{"name": "Go Book", "price": 24.99, "stock": 10}') print(repr(p2)) # Product(name='Go Book', price=24.99, stock=10) # Los mixins siguen el MRO correctamente print([c.__name__ for c in Product.__mro__]) # ['Product', 'JSONMixin', 'ReprMixin', 'ValidatedMixin', 'object']
SalidaProduct(name='Python Handbook', price=29.99, stock=50) {"name": "Python Handbook", "price": 29.99, "stock": 50} True Product(name='Go Book', price=24.99, stock=10) ['Product', 'JSONMixin', 'ReprMixin', 'ValidatedMixin', 'object']
Convención de nombres para mixins

Por convención, los mixins llevan el sufijo Mixin en su nombre (JSONMixin, LogMixin, TimingMixin). Esto señala que no deben instanciarse solos y que son piezas de funcionalidad para componer con otras clases.

Practica