R

ReflexKR

Documentación de Reflex

El aeropuerto de las etiquetas: el modelo de objetos de Python

·

Resumen del curso

1

La etiqueta no es la maleta: nombres y objetos

2

Maletas selladas: mutable e inmutable

3

Maletas dentro de maletas: contenedores y copias

4

Al entregar una maleta: funciones y argumentos

5

Zonas y pases: ámbitos

6

Equipaje de mano: clausuras

7

La especificación de la maleta: clases e instancias

8

Procedimientos estándar del aeropuerto: métodos dunder

9

La especificación de la especificación: herencia y MRO

10

El control: el secreto del acceso a atributos

11

Maletas sin dueño: memoria y GC

12

Números de taquilla: hash, igualdad y repaso final

El aeropuerto de las etiquetas: el modelo de objetos de Python
Lección 9

Tiempo 6:40–7:30

La especificación de la especificación: herencia y MRO

Parte de equipaje perdido

LOST-009

Una de las dos normas desapareció

Lo reporta

Han, del equipo de desarrollo de normativa de carga aérea

Qué se perdió

La inspección de mercancías peligrosas de PowerBank, la especificación de las baterías externas. La hizo heredar tanto la norma de «equipaje de mano» como la de «mercancías peligrosas», pero en el resultado de la inspección no aparece la parte de mercancías peligrosas.

Para cerrar el caso

Desvelar el orden en que PowerBank lee las normas (MRO), y arreglarlo para que se apliquen las dos.

Aeropuerto Internacional Haneulseom · Centro de equipajes

Han ha creado una especificación nueva para las baterías externas. Como la hizo heredar tanto de la especificación de equipaje de mano como de la de mercancías peligrosas, contaba con que se aplicaran las dos inspecciones. Pero en la hoja de inspección solo aparece la comprobación de tamaño, y falta la de mercancías peligrosas.

La supervisora Do Miran pone una especificación encima de la otra. «Cuando hay dos especificaciones superiores, está fijado cuál se lee primero. Empecemos por ese orden.»

9-1. Especificaciones superiores: herencia y sobrescritura (7 min)

class CarryOn(Item): significa «lo que no esté en CarryOn, búscalo en Item». Ese orden de búsqueda está escrito en el __mro__ (Method Resolution Order) de la clase.

class Item:
    kind = "item"
    def label(self):
        return f"[{self.kind}]"

class CarryOn(Item):
    kind = "carry-on"

b = CarryOn()
print(b.label())
# [carry-on]
print(CarryOn.__mro__)
# (<class '__main__.CarryOn'>, <class '__main__.Item'>, <class 'object'>)
print("label" in vars(CarryOn), "label" in vars(Item))
# False True

Leer b.label() funciona como en la lección 7. Como no está en el __dict__ de la instancia, se recorre __mro__ desde el principio. CarryOn no tiene label, y se encuentra en Item. Pero el self.kind de dentro de label vuelve a buscarse empezando por la instancia, así que sale "carry-on", el CarryOn.kind.

Sobrescribir (override) no es ninguna sintaxis especial. Si escribes el mismo nombre en la especificación hija, gana lo que se encuentra primero en el MRO. Si quieres aprovechar lo del padre, lo llamas con super().

class Fragile(Item):
    kind = "fragile"
    def label(self):
        return super().label() + "!"

print(Fragile().label())
# [fragile]!

El MRO de toda clase termina en object. De ahí vienen todas las asas básicas, como __repr__ o __eq__ (lección 8).

9-2. Herencia múltiple y el diamante (8 min)

La especificación del parte hereda de dos especificaciones. Las dos heredan de Item, así que, dibujado, forma un rombo (un diamante).

class Item:
    rule = "item"
    tag = "plain"
class CarryOn(Item):
    rule = "carry-on"
class Hazmat(Item):
    rule = "hazmat"
    tag = "red"
class PowerBank(CarryOn, Hazmat):
    pass

print([c.__name__ for c in PowerBank.__mro__])
# ['PowerBank', 'CarryOn', 'Hazmat', 'Item', 'object']
print(PowerBank.rule, PowerBank.tag)
# carry-on red
  • rule se encontró primero en CarryOn. Va primero el padre de la izquierda dentro del paréntesis.
  • tag no está en CarryOn, así que se encontró en la siguiente posición, Hazmat. No se baja hasta el "plain" de Item.
  • Item aparece una sola vez, y después de que hayan terminado los dos hijos. No es una búsqueda en profundidad que baje hasta el fondo por el camino de la izquierda.

En el explorador de abajo, cambia el orden de los padres y prueba también el caso de la última especificación, en el que no se puede construir un orden.

Explorador de MRO

Atributo

Orden de búsqueda (__mro__)

No está en ninguna parte

Pasos de C3
Con (CarryOn, Hazmat), rule se encuentra en CarryOn; con (Hazmat, CarryOn), en Hazmat. Con (Item, Hazmat) no se puede construir el MRO y la clase ni siquiera llega a crearse.

9-3. super() no es el padre, sino la siguiente posición (10 min)

Ahora, el código real del parte. Cada norma devuelve sus elementos de inspección y les añade los de la norma de arriba.

class Item:
    def check(self):
        return ["item"]
class CarryOn(Item):
    def check(self):
        return ["size"] + Item.check(self)
class Hazmat(Item):
    def check(self):
        return ["fire"] + super().check()
class PowerBank(CarryOn, Hazmat):
    pass

print(PowerBank().check())
# ['size', 'item']

Quien escribió CarryOn pensó «mi padre es Item» y llamó directamente a Item.check(self). Usada sola, es correcto. Pero en el MRO de PowerBank, la posición siguiente a CarryOn no es Item, sino Hazmat. Al llamarlo directamente, se saltó Hazmat.

super() no es «mi padre». Es la posición que sigue a la clase actual en el MRO de type(self). El mismo CarryOn.check tiene una posición siguiente distinta según quién lo llame.

class CarryOn(Item):
    def check(self):
        return ["size"] + super().check()
class PowerBank(CarryOn, Hazmat):
    pass

print(PowerBank().check())
# ['size', 'fire', 'item']
print(CarryOn().check())
# ['size', 'item']
print(super(CarryOn, PowerBank()).check())
# ['fire', 'item']
  • Con PowerBank(), la cadena sigue el orden CarryOn → Hazmat → Item.
  • Con CarryOn() sola, el MRO es CarryOn, Item, object, así que super() es precisamente Item.
  • super(CarryOn, obj) escribe explícitamente «busca en el MRO de obj a partir de lo que va después de CarryOn». El super() sin argumentos rellena esos dos valores por su cuenta.

Compara las dos versiones en el tablero de abajo. Fíjate en cómo se apilan las casillas de llamada y en el número de la línea que se está ejecutando.

Tablero de etiquetas

Paso 1 / 0

Lo siguiente que se ejecuta: 0

Preparando el laboratorio…

Acaba de cambiar

Maleta sellada (inmutable)

Con super(), el indicador de línea baja 6 → 9 → 3 y se apilan tres casillas check, y el self de las tres es la misma maleta PowerBank. Con Item.check(self) salta directamente 6 → 3 y solo hay dos casillas. La celda __class__ que lleva la función que usa super() le dice «en qué clase está ahora».

La cadena solo se mantiene si todos llaman a super(). Si falta uno solo, las posiciones de detrás no se ejecutan. Con __init__ pasa lo mismo. Si cada especificación recibe argumentos distintos, cada una saca los suyos y pasa el resto a la siguiente posición con **kw.

class Item:
    pass
class CarryOn(Item):
    def __init__(self, cm=55, **kw):
        self.cm = cm
        super().__init__(**kw)
class Hazmat(Item):
    def __init__(self, wh=100, **kw):
        self.wh = wh
        super().__init__(**kw)
class PowerBank(CarryOn, Hazmat):
    pass

print(vars(PowerBank(cm=20, wh=99)))
# {'cm': 20, 'wh': 99}

CarryOn.__init__ solo saca cm y pasa wh=99. La siguiente posición, Hazmat, lo recibe, y el último, object.__init__(), termina sin argumentos.

Pin dice

→ Si dentro de un método llamas al padre escribiendo su nombre (Item.check(self)), con herencia múltiple te saltas las especificaciones que quedan en medio. Para encadenar llamadas, usa siempre super().

9-4. Linealización C3: la regla que fija el orden (6 min)

Python construye el MRO con un método llamado linealización C3. Cumple tres promesas:

  1. El hijo siempre va antes que sus padres.
  2. Se respeta el orden de los padres escrito en el paréntesis.
  3. También se respeta el orden del MRO de cada padre.

El cálculo de PowerBank(CarryOn, Hazmat) es así. Se ponen en fila los MRO de los padres y la lista de padres.

Listas restantesElegidaMotivo
[CarryOn, Item, object] [Hazmat, Item, object] [CarryOn, Hazmat]CarryOnEs la primera cabeza. No está en la cola de ninguna otra lista
[Item, object] [Hazmat, Item, object] [Hazmat]HazmatItem está en la cola de la segunda lista, así que se aplaza
[Item, object] [Item, object]ItemYa no está en ninguna cola
[object] [object]object

El resultado es CarryOn, Hazmat, Item, object después de PowerBank. Si despliegas «Pasos de C3» en el explorador, verás el mismo proceso en cuatro líneas. En cada línea, lo primero es la clase elegida en ese paso, y lo demás, las listas que quedaban en ese momento.

Si no se pueden cumplir todas las promesas, la clase no se crea.

class Item:
    pass
class Hazmat(Item):
    pass
class Odd(Item, Hazmat):
    pass
# TypeError: Cannot create a consistent method resolution order (MRO) for bases Item, Hazmat

Según el orden del paréntesis, Item tiene que ir antes que Hazmat (promesa 2), y según la regla de que el hijo va primero, Hazmat tiene que ir antes que Item (promesa 1). No hay ninguna fila que cumpla las dos. La solución es sencilla: pon a la izquierda el padre más específico.

9-5. Mixins, isinstance y composición (6 min)

Un mixin es una clase pequeña que no se usa sola, sino que añade una funcionalidad a otra clase. Se le pone Mixin al final del nombre y se coloca a la izquierda en la lista de padres, para que se encuentre antes en el MRO.

class ReprMixin:
    def __repr__(self):
        fields = ", ".join(f"{k}={v!r}" for k, v in vars(self).items())
        return f"{type(self).__name__}({fields})"

class Tag(ReprMixin):
    def __init__(self, code, kg):
        self.code, self.kg = code, kg

print([Tag("ICN", 12)])
# [Tag(code='ICN', kg=12)]

Explorador de MRO

Atributo

Orden de búsqueda (__mro__)

No está en ninguna parte

Pasos de C3
Si pones LabelMixin a la izquierda, describe se encuentra en el mixin; si lo pones a la derecha, se encuentra antes el de Item y el describe del mixin queda tapado. prefix, que no se solapa, se encuentra en los dos casos.

isinstance(obj, C) mira si C está en el MRO de type(obj). issubclass pregunta lo mismo entre clases. Por eso es más amplio que type(x) is C.

class Item:
    pass
class Hazmat(Item):
    pass
class PowerBank(Hazmat):
    pass

pb = PowerBank()
print(isinstance(pb, Item), issubclass(PowerBank, Hazmat), isinstance(pb, (int, Hazmat)))
# True True True
print(type(pb) is Hazmat)
# False

La herencia se usa cuando «X es un tipo de Y». Si «X tiene un Y», es mejor la composición (guardar otro objeto como atributo). Si las normas del parte se guardaran como una lista de objetos norma en vez de con herencia, el orden estaría a la vista y no habría que pensar en el MRO.

class Rule:
    def __init__(self, name, test):
        self.name, self.test = name, test

class Inspector:
    def __init__(self, *rules):
        self.rules = list(rules)
    def failed(self, item):
        return [r.name for r in self.rules if not r.test(item)]

size = Rule("size", lambda it: it["cm"] <= 55)
fire = Rule("fire", lambda it: it["wh"] <= 100)
print(Inspector(size, fire).failed({"cm": 20, "wh": 160}))
# ['fire']

9-6. Especificaciones abstractas y registro de especificaciones hijas (3 min)

Si heredas de abc.ABC y pones @abstractmethod, ese método se convierte en «una casilla que el hijo tiene que rellenar sí o sí». Un hijo que no la rellene no puede crear instancias. __init_subclass__ es un asa que se llama en el padre cada vez que se crea una clase hija, así que sirve para montar un registro de especificaciones.

from abc import ABC, abstractmethod

class Rule(ABC):
    registry = []
    def __init_subclass__(cls, **kw):
        super().__init_subclass__(**kw)
        Rule.registry.append(cls.__name__)
    @abstractmethod
    def check(self, item): ...

class SizeRule(Rule):
    def check(self, item):
        return item["cm"] <= 55
class FireRule(Rule):
    pass

print(Rule.registry, SizeRule().check({"cm": 40}))
# ['SizeRule', 'FireRule'] True
FireRule()
# TypeError: Can't instantiate abstract class FireRule without an implementation for abstract method 'check'

Caso cerrado (10 min)

Cambiamos el Item.check(self) de CarryOn por super().check() y volvemos a sacar la hoja de inspección junto con el MRO.

class Item:
    def check(self):
        return ["item"]
class CarryOn(Item):
    def check(self):
        return ["size"] + super().check()
class Hazmat(Item):
    def check(self):
        return ["fire"] + super().check()
class PowerBank(CarryOn, Hazmat):
    pass

print([c.__name__ for c in PowerBank.__mro__])
# ['PowerBank', 'CarryOn', 'Hazmat', 'Item', 'object']
print(PowerBank().check())
# ['size', 'fire', 'item']

fire ha vuelto a la hoja de inspección. Han asiente. «No había que llamar al padre, sino al siguiente de la fila.» La supervisora vuelve a grapar las dos especificaciones. «El orden lo decide Python. Nosotros solo tenemos que no romper la cadena.»

Informe de cierre — LOST-009. CarryOn llamaba al padre por su nombre y se saltaba Hazmat, la siguiente posición del MRO. Resuelto haciendo que todas las especificaciones llamen a super().

Ejercicios

  1. Calcula primero con una tabla C3 el MRO de class A: pass, class B(A): pass, class C(A): pass y class D(C, B): pass, y luego compruébalo con D.__mro__.
  2. En el código del caso cerrado, si solo cambias Hazmat.check por return ["fire"] (sin llamar a super()), predice cómo quedará la hoja de inspección.
  3. Añade una norma de peso al Inspector de 9-5, cambia el orden de las normas y comprueba cómo cambia el orden de la lista resultante.

La libreta de la supervisora

  • Los atributos se buscan en el orden de Cls.__mro__. Sobrescribir no es más que «gana el primero que se encuentra».

  • En la herencia múltiple, el padre común aparece una sola vez, después de todos los hijos (linealización C3).

  • super() no es el padre, sino la siguiente posición en el MRO de type(self). La cadena solo se mantiene si todos lo llaman.

  • En la cadena de __init__, cada uno saca sus argumentos y pasa el resto con **kw.

  • Si no se puede construir el MRO, la clase ni siquiera se crea. El padre más específico, a la izquierda.

  • Los mixins, a la izquierda. isinstance mira el MRO. Si es «tiene un», composición mejor que herencia.

← Anterior

Lección 8 · Procedimientos estándar del aeropuerto: métodos dunder

Siguiente →

Lección 10 · El control: el secreto del acceso a atributos

Recibe avisos de nuevas lecciones

Te avisaremos por correo cuando este curso tenga lecciones nuevas o se abra un curso de continuación.

Datos: tu correo electrónico · Finalidad: avisos de nuevas lecciones o de la publicación de este curso · Conservación: hasta que pidas que lo borremos. No lo usamos para nada más ni lo compartimos. Responde a cualquier aviso y lo borraremos enseguida.

Preguntas y respuestas

Pregunta lo que quieras sobre esta lección. Aquí se reúnen las preguntas de todas las versiones de idioma de esta página.

Se muestra como texto plano, sin enlaces ni formato.

R

ReflexKR

Aprende Python y Reflex haciendo. Todos los ejemplos se ejecutaron y verificaron en Reflex 0.10.0.

ReflexKR es gratis y sin banners publicitarios. Si te ayudó, puedes apoyarlo con un café. ☕ Invítanos un café

Aviso de afiliados
한국어English日本語简体中文EspañolDeutsch

© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0

Built with Reflex