Herencia múltiple en Python: cómo el MRO (Method Resolution Order) decide qué constructor usar
Índice de contenido
En este articulo, explicaremos como podemos invocar a los métodos constructores de clases cuando tenemos herencia multiple en Python.
Estamos trabajando con un sistema de tres clases anidadas principales:
Vista → La capa que consume el usuario final.
Intermedia → Decide si llamar PayPal o Stripe.
Pasarela de pago → En este caso, PayPal.
class PaymentPaypalClient(AbstractPayment):
def __init__(self):
super().__init__()
***
class BasePayment(PaymentPaypalClient, PaymentStripeClient):
def __init__(self):
super().__init__()
class PaymentBookView(LoginRequiredMixin, View, BasePayment):
def __init__(self):
# super().__init__()
BasePayment.__init__(self) En Django, la vista cumple el papel del controlador (similar a frameworks como CodeIgniter, Laravel u otros).
Herencia Múltiple en Python
Lo bonito de Python es que contamos con herencia múltiple, sin los problemas de PHP (traits), Dart (mixins) o Kotlin (mixins). Basta con colocar la clase, una coma y seguir con la vida.
El problema surge cuando necesitamos inicializar métodos constructores (__init__).
En este caso, PayPal necesita inicializar su constructor porque allí configuramos el entorno, las claves y la URL base (dependiendo de si estamos en desarrollo o producción):
class PaymentPaypalClient(AbstractPayment):
def __init__(self):
self.base_url = (
"https://api-m.paypal.com"
if env
else "https://api-m.sandbox.paypal.com"
)
self.client_id = settings.PAYPAL_CLIENT_ID
self.secret = settings.PAYPAL_SECRETEl inconveniente es que no estábamos llamando los constructores de dichas clases. Eso provoca que, al procesar la orden de PayPal, falle con errores como el de base_url no definido.
Este error es un clásico de la herencia múltiple en Python y tiene que ver con el MRO (Method Resolution Order), es decir, el orden en que Python resuelve la cadena de herencia.
Explicación del MRO
En pocas palabras:
- Python toma la primera clase listada en la herencia.
- Si no encuentra constructor, pasa a la siguiente.
- Si lo encuentra, lo ejecuta y se detiene, salvo que ese constructor llame explícitamente a super().
- En este caso, la vista tenía un constructor, pero no llamaba a la superclase, lo que cortaba la cadena. Por eso nunca llegaba al constructor de BasePayment ni al de PayPal.
En el caso de:
class PaymentBookView(LoginRequiredMixin, View, BasePayment):
def __init__(self):
# super().__init__()
BasePayment.__init__(self) Solamente llama al primer constructor, que seria el de la clase View ya que el mixin LoginRequiredMixin no tiene constructor:
class LoginRequiredMixin(AccessMixin):
"""Verify that the current user is authenticated."""
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
class View:
***
def __init__(self, **kwargs):
"""
Constructor. Called in the URLconf; can contain helpful extra
keyword arguments, and other things.
"""Y nunca e llama el constructor de la clase BasePayment y con esto el constructor de la clase PaymentPaypalClient y no se inicializan los parámetro del base URL y claves de PayPal; para evitar esto, tenemos dos formas, o acomodamos el orden de las clases heredadas:
Existen dos formas de resolverlo:
- Reordenar las clases en la herencia → Por ejemplo, poner BasePayment antes de la vista: class PaymentBookView(LoginRequiredMixin, BasePayment, View)
- Llamar directamente al constructor de la clase requerida → BasePayment.__init__(self, ...):
class PaymentBookView(LoginRequiredMixin, View, BasePayment):
def __init__(self):
BasePayment.__init__(self) De esta manera garantizamos que los constructores realmente se ejecuten en el orden esperado.
Conclusión
El problema estaba en no llamar correctamente a la superclase en un contexto de herencia múltiple.
El MRO define el orden de ejecución, pero si una clase interrumpe la cadena, los demás constructores nunca se ejecutan.
La solución es sencilla: llamar explícitamente al constructor correcto o reordenar la herencia según lo que necesitemos.
En resumen: si quieres evitar este tipo de errores en Python, asegúrate de entender cómo funciona el MRO y cómo se ejecutan los constructores en la herencia múltiple.
Si aun no entiendes, puedes hacer algunos debug colocando la impresión en los constructores, ve comentando o descomentando los constructores de las partes del código que tengas dudas de como se ejecutan:
class PaymentPaypalClient(AbstractPayment):
def __init__(self):
print('THREE')
super().__init__()
class BasePayment(PaymentPaypalClient, PaymentStripeClient):
def __init__(self):
print('TWO')
super().__init__()
class PaymentBookView(LoginRequiredMixin, View, BasePayment):
def __init__(self):
print('ONE')
BasePayment.__init__(self)
Acepto recibir anuncios de interes sobre este Blog.
El MRO (Method Resolution Order) en Python define cómo se ejecutan los constructores en herencia múltiple. Si una clase no llama a super(), corta la cadena y otros constructores no se ejecutan.