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 5:00–5:50
Parte de equipaje perdido
LOST-007
Lo reporta
Moon, responsable del transporte de la gira de una orquesta
Qué se perdió
Puso una pegatina de «frágil» en un estuche de instrumento, y esa pegatina aparece en todos los estuches hechos con la misma especificación.
Para cerrar el caso
Encontrar dónde está realmente la lista de pegatinas, y hacer que cada estuche tenga su propia lista.
Aeropuerto Internacional Haneulseom · Centro de equipajes
En el cajón del escritorio de la supervisora Do Miran hay un fajo de especificaciones de maletas. Con una sola hoja de especificación se pueden fabricar tantas maletas iguales como quieras. «La especificación es una hoja; las maletas, una a una. Las dos son cosas.»
Moon dice que solo puso «frágil» en el estuche del violín, pero que la misma pegatina se ve también en el del violonchelo. La supervisora no abre los estuches, sino la especificación. La pegatina estaba pegada ahí.
La sentencia class es una asignación que crea un objeto clase y lo ata a un nombre (igual que el def de la lección 1). Una clase también es un objeto con su tipo, y ese tipo es type.
class Case:
size = "M"
def __init__(self, owner):
self.owner = owner
print(type(Case), isinstance(Case, type))
# <class 'type'> True
b = Case("cello")
print(type(b).__name__, type(b) is Case)
# Case True
print(vars(b))
# {'owner': 'cello'}
print([k for k in vars(Case) if not k.startswith("__")])
# ['size']
print(b.size, "size" in vars(b))
# M False| Qué guarda | Cómo verlo | |
|---|---|---|
| Clase (especificación) | Los nombres creados en el cuerpo de la clase: atributos de clase, métodos | vars(Case), Case.__dict__ |
| Instancia (maleta) | Los atributos propios de esa maleta. Normalmente con self.x = ... en __init__ | vars(b), b.__dict__ |
__dict__ de b solo está owner. Aun así, b.size se lee como "M".__dict__ de la instancia y, si no está, la clase. Si no está en la maleta, se consulta la especificación.__dict__ de la clase es una ventana de solo lectura llamada mappingproxy. Para cambiar un atributo de clase, asígnalo directamente en la clase, como Case.size = "L".Esta es la especificación del parte:
class Case:
stickers = []
def __init__(self, owner):
self.owner = owner
a = Case("violin")
b = Case("cello")
a.stickers.append("FRAGILE")
print(b.stickers, Case.stickers)
# ['FRAGILE'] ['FRAGILE']
print(a.stickers is b.stickers, "stickers" in vars(a))
# True Falsestickers = [] se ejecuta una sola vez en el cuerpo de la clase. Solo hay una lista, pegada a la especificación.a.stickers no está en el __dict__ de a, así que se busca en la clase, y append modifica esa única lista (cambiar la maleta).Lo que cada instancia debe tener por separado se le pone a self en __init__. Compara abajo las tres versiones.
Tablero de etiquetas
Paso 1 / 0
Lo siguiente que se ejecuta: 0
Preparando el laboratorio…
Acaba de cambiar
Maleta sellada (inmutable)
Como en la tercera versión, si asignas a la instancia un atributo con el mismo nombre, el atributo de clase se queda como estaba y aparece un nombre nuevo en el __dict__ de la instancia. En esa instancia, el atributo de clase queda tapado (con la misma forma que tapar nombres integrados en la lección 5).
class Case:
size = "M"
a = Case()
b = Case()
a.size = "L"
print(a.size, b.size, vars(a))
# L M {'size': 'L'}
Case.size = "S"
print(a.size, b.size)
# L S
del a.size
print(a.size)
# Sb).del a.size, vuelve a verse el valor de la clase.No olvides que += también es una asignación. Si usas += con un atributo de clase numérico, aparece un nombre nuevo en la instancia.
class Gate:
count = 0
g = Gate()
g.count += 1
print(g.count, Gate.count, vars(g))
# 1 0 {'count': 1}g.count += 1 es g.count = g.count + 1. El lado derecho lee 0 de la clase y el izquierdo pone 1 en el __dict__ de g. Si es un número que cuentan todas las instancias juntas, hay que asignarlo directamente en la clase, como en Gate.count += 1.
Pin dice
→ Si pones una lista o un diccionario en el cuerpo de la clase, lo comparten todas las instancias. Deja en la clase solo lo que de verdad se comparte, como constantes o valores por defecto, y lo que cambia de maleta a maleta pónselo a self en __init__.
Veamos en el «Flujo del control» el camino que recorre Python al leer b.x. En esta lección solo hace falta fijarse en el paso 2 (el __dict__ de la instancia) y el paso 4 (un atributo normal de la clase). Los pasos 1, 3 y 5 se ven en la lección 10.
El b de abajo tiene size tanto en la clase como en su propio __dict__.
Flujo del control
1
class Case:
2
size = "M"
3
stickers = []
4
def __init__(self, owner):
5
self.owner = owner
6
def label(self):
7
return "case of " + self.owner
8
9
b = Case("cello")
10
b.size = "L"
b.
¿Descriptor de datos en la clase (__get__ + __set__)?
¿En el __dict__ de la instancia?
¿Descriptor sin datos en la clase (solo __get__, como una función)?
¿Un atributo normal de la clase?
¿Hay __getattr__?
AttributeError
Resultado: b. →
size también está en la clase, pero se encuentra antes en el paso 2, así que vale 'L'. El Case.size entre paréntesis en la fila del resultado indica que «la clase también tiene un nombre igual».label es una función que está en la clase. Las funciones son objetos especiales que se detienen en el paso 3: al sacarlas no se obtiene una función sin más, sino un bound method. Es el tema del siguiente apartado.Un método es una función normal que está en la clase. Cuando lo sacas a través de una instancia, Python crea un objeto nuevo que une la función y la instancia: un método ligado (bound method).
class Bag:
def __init__(self, owner):
self.owner = owner
def label(self):
return "bag of " + self.owner
bag = Bag("kim")
print(bag.label is bag.label, bag.label == bag.label)
# False True
print(bag.label.__self__ is bag, bag.label.__func__ is Bag.label)
# True True
print(bag.label(), Bag.label(bag))
# bag of kim bag of kim
print(type(bag.label).__name__, type(Bag.label).__name__)
# method functionbag.label se crea un método ligado nuevo. Por eso is da False. Como une la misma función y la misma instancia, == da True.__self__ es la instancia ligada y __func__, la función original de la clase.bag.label() es lo mismo que Bag.label(bag). self no es más que el primer argumento, que el método ligado rellena por ti.Tablero de etiquetas
Paso 1 / 0
Lo siguiente que se ejecuta: 0
Preparando el laboratorio…
Acaba de cambiar
Maleta sellada (inmutable)
vars, getattr, setattr, hasattr (5 min)A veces el nombre de un atributo hay que decidirlo como cadena durante la ejecución: las claves de un archivo de configuración, el nombre de una columna elegida en pantalla…
class Bag:
def __init__(self, owner):
self.owner = owner
bag = Bag("kim")
setattr(bag, "color", "red")
print(getattr(bag, "color"), hasattr(bag, "weight"), getattr(bag, "weight", 0))
# red False 0
print(vars(bag))
# {'owner': 'kim', 'color': 'red'}
vars(bag)["tag"] = "T1"
print(bag.tag, vars(bag) is bag.__dict__)
# T1 True
bag.weight
# AttributeError: 'Bag' object has no attribute 'weight'setattr(obj, "x", v) equivale a obj.x = v, y getattr(obj, "x"), a obj.x. El tercer argumento de getattr es el valor por defecto que se devuelve si el atributo no existe.hasattr decide intentando de verdad un getattr y mirando si salta AttributeError.vars() de una instancia no es una instantánea, sino el __dict__ de verdad. No es como el locals() dentro de una función de la lección 5.bag.colour = ...) también se convierte en silencio en un atributo nuevo.__slots__ y otras novedades (5 min)Si escribes los nombres de los atributos en __slots__, las instancias no tienen __dict__ y solo pueden usar esos nombres. En CPython, como cada instancia no lleva diccionario, también consume menos memoria.
class Slim:
__slots__ = ("owner",)
def __init__(self, owner):
self.owner = owner
s = Slim("kim")
print(s.owner, hasattr(s, "__dict__"))
# kim False
s.color = "red"
# AttributeError: 'Slim' object has no attribute 'color' and no __dict__ for setting new attributesDesde 3.13, las clases tienen __static_attributes__. Reúne, ordenados, los nombres a los que los métodos de la clase asignan un valor con self.nombre = ....
class Trunk:
def __init__(self, owner):
self.owner = owner
self.tags = []
def pack(self):
self.weight = 10
print(Trunk.__static_attributes__)
# ('owner', 'tags', 'weight')Por último, la evaluación diferida de anotaciones de 3.14 (PEP 649 y 749). Las anotaciones del cuerpo de una clase no se evalúan al crear la clase, sino la primera vez que se lee __annotations__. Por eso la clase se crea aunque la anotación use un nombre que todavía no está definido. Hasta 3.13, la línea friend: Missing lanzaba un NameError.
class Partner:
friend: Missing
print("created", Partner.__name__)
# created Partner
class Missing:
pass
print(Partner.__annotations__)
# {'friend': <class '__main__.Missing'>}Quitamos la lista de pegatinas de la especificación y le damos una a cada estuche. Lo que sí se comparte (el tamaño de la especificación) se queda en la clase.
class Case:
size = "M"
def __init__(self, owner):
self.owner = owner
self.stickers = []
a = Case("violin")
b = Case("cello")
a.stickers.append("FRAGILE")
print(a.stickers, b.stickers)
# ['FRAGILE'] []
print(vars(a))
# {'owner': 'violin', 'stickers': ['FRAGILE']}
print(Case.__static_attributes__)
# ('owner', 'stickers')Ahora «frágil» solo aparece en el estuche del violín. Cuando Moon se marcha, Pin muestra en pantalla el registro de las maletas fantasma de la cinta 13. Las dos maletas se fabricaron con la misma especificación, y el atributo partner de una apunta a la otra, y el partner de la otra apunta de nuevo a la primera. La supervisora rodea esa línea con el bolígrafo.
Informe de cierre — LOST-007. La lista de pegatinas era un atributo de clase, así que todos los estuches compartían la misma lista. Resuelto creando una lista nueva para cada estuche con self.stickers = [] en __init__.
Case.stickers = [] en la clase, si ejecutas a.stickers += ["FRAGILE"], ¿cómo quedan b.stickers y vars(a)? (Pista: ocurren a la vez el += de la lección 2 y el += de 7-2.)__slots__ = ("owner",) pones el atributo de clase size = "M", ¿se puede leer s.size desde una instancia? ¿Y s.size = "L"?getattr, por orden, los tres atributos ["owner", "color", "weight"] y los imprima, mostrando "-" para los que no existan.La libreta de la supervisora
Las clases también son objetos. type(Case) es type. La sentencia class también es una asignación que ata un nombre.
Cada instancia tiene su propio __dict__. Se lee en el orden instancia → clase.
Los atributos de clase los comparten todas las instancias. Si pones un objeto mutable, el cambio en un sitio se ve en todos.
Asignar a la instancia un nombre igual tapa el atributo de clase. += también es una asignación.
bag.m da un método ligado nuevo cada vez que se saca (is da False, == da True). __self__, __func__.
getattr / setattr / hasattr manejan nombres como cadenas. vars(obj) es el __dict__ de verdad.
__slots__ impide atributos nuevos. __static_attributes__ (3.13), evaluación diferida de anotaciones (3.14).
← Anterior
Lección 6 · Equipaje de mano: clausuras
Siguiente →
Lección 8 · Procedimientos estándar del aeropuerto: métodos dunder
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.