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
Tiempo 6:40–7:30
Parte de equipaje perdido
LOST-009
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.»
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 TrueLeer 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).
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 redrule 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
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']PowerBank(), la cadena sigue el orden CarryOn → Hazmat → Item.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)
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().
Python construye el MRO con un método llamado linealización C3. Cumple tres promesas:
El cálculo de PowerBank(CarryOn, Hazmat) es así. Se ponen en fila los MRO de los padres y la lista de padres.
| Listas restantes | Elegida | Motivo |
|---|---|---|
[CarryOn, Item, object] [Hazmat, Item, object] [CarryOn, Hazmat] | CarryOn | Es la primera cabeza. No está en la cola de ninguna otra lista |
[Item, object] [Hazmat, Item, object] [Hazmat] | Hazmat | Item está en la cola de la segunda lista, así que se aplaza |
[Item, object] [Item, object] | Item | Ya 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, HazmatSegú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.
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
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)
# FalseLa 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']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'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().
class A: pass, class B(A): pass, class C(A): pass y class D(C, B): pass, y luego compruébalo con D.__mro__.Hazmat.check por return ["fire"] (sin llamar a super()), predice cómo quedará la hoja de inspección.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.
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© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0
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.