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 2:30–3:20
Parte de equipaje perdido
LOST-004
Lo reporta
Jin, desarrolladora en una startup de reparto a domicilio
Qué se perdió
Una lista de pedidos nueva. Cada vez que llama a la función que crea la lista de pedidos, sale con los pedidos de los clientes anteriores acumulados.
Para cerrar el caso
Encontrar dónde se quedaban guardados los pedidos anteriores, y hacer que cada llamada empiece con una lista vacía.
Aeropuerto Internacional Haneulseom · Centro de equipajes
A la hora de comer, la desarrolladora entra corriendo con su portátil. El pedido del primer cliente era un solo café, pero en el recibo del segundo cliente aparecen el café y un bagel.
La supervisora Do Miran señala con el dedo una línea de la definición de la función. «Cuando entregas una maleta, no entregas la maleta: se le cuelga una etiqueta más. Y esta maleta vacía se compró una sola vez, al crear la función.»
Cuando llamas a una función, el objeto que pasas como argumento no se copia. El nombre del parámetro dentro de la función se cuelga como una etiqueta más del mismo objeto. Esto se llama paso por referencia a objeto (call by object reference, call by sharing).
mine = ["laptop"]
def show(bag):
print(bag is mine)
show(mine)
# TrueComo solo se ha colgado una etiqueta más, se aplican tal cual las dos operaciones de la lección 1.
bag.append(...), el cambio se ve también desde el nombre de fuera. Es la misma maleta.bag = ..., solo se mueve la etiqueta de dentro de la función. El nombre de fuera sigue colgado de la maleta original.def pack(bag):
bag.append("charger")
def swap(bag):
bag = ["new"]
bag.append("charger")
mine = ["laptop"]
pack(mine)
print(mine)
# ['laptop', 'charger']
swap(mine)
print(mine)
# ['laptop', 'charger']En el tablero de abajo aparece por primera vez una casilla de llamada (marco). Al llamar a la función se crea a la izquierda la casilla pack, y la etiqueta bag que hay dentro apunta a la misma maleta que mine, fuera. Cuando la función termina, la casilla desaparece.
Tablero de etiquetas
Paso 1 / 0
Lo siguiente que se ejecuta: 0
Preparando el laboratorio…
Acaba de cambiar
Maleta sellada (inmutable)
Aquí vuelve a aparecer el += de la lección 2. El mismo += se ve desde fuera o no según el tipo.
def bump(n):
n += 1 # int: objeto nuevo, solo se mueve la etiqueta de dentro de la función
def extend(lst):
lst += [1] # list: __iadd__, modifica la misma maleta
count, items = 0, []
bump(count)
extend(items)
print(count, items)
# 0 [1]En Python no existen los «argumentos que modifican una variable de fuera». Una función solo puede influir en lo de fuera modificando un objeto mutable que ha recibido o devolviendo un valor.
Pin dice
→ No te líes con «¿paso por valor o por referencia?». Python siempre hace lo mismo: cuelga una etiqueta más, el parámetro, del objeto recibido. Que se vea desde fuera depende de si la función modificó ese objeto o solo movió la etiqueta.
Reducida al mínimo, la función del parte es esta:
def f(x=[]):
x.append(1)
return x
print(f())
# [1]
print(f())
# [1, 1]
print(f.__defaults__)
# ([1, 1],)El valor por defecto [] no se evalúa al llamar a la función, sino una sola vez, cuando se ejecuta la sentencia def. La lista creada así queda colgada de la tupla __defaults__ del objeto función y permanece mientras la función exista. Cada vez que llamas omitiendo el argumento, el parámetro x se cuelga de esa única lista.
print(f() is f.__defaults__[0])
# TrueSigue las dos llamadas en el tablero de abajo. Las casillas de llamada aparecen y desaparecen, pero la lista colgada de defaults se queda y los pedidos se acumulan.
Tablero de etiquetas
Paso 1 / 0
Lo siguiente que se ejecuta: 0
Preparando el laboratorio…
Acaba de cambiar
Maleta sellada (inmutable)
La solución es siempre la misma. Pon None como valor por defecto y crea una lista nueva dentro de la función cada vez.
def add_order(item, orders=None):
if orders is None:
orders = []
orders.append(item)
return orders
print(add_order("coffee"), add_order("bagel"))
# ['coffee'] ['bagel']
print(add_order.__defaults__)
# (None,)Si None es un valor con significado y hay que distinguirlo de «no se pasó el argumento», crea un objeto que nadie más use y utilízalo como centinela (sentinel). Es un objeto único, así que se compara con is.
_MISSING = object()
def lookup(key, default=_MISSING):
if default is _MISSING:
return f"no default for {key}"
return default
print(lookup("gate"), lookup("gate", None))
# no default for gate None[], {} o set() como valores por defecto. La regla de que se evalúan una sola vez en el def se aplica igual a todos los valores por defecto.f.__kwdefaults__.Ayuda pensar en dos tipos de funciones: las que modifican en el sitio la maleta recibida y las que dejan el original y devuelven una maleta nueva.
nums = [3, 1, 2]
print(nums.sort(), nums)
# None [1, 2, 3]
nums = [3, 1, 2]
out = sorted(nums)
print(out, nums, out is nums)
# [1, 2, 3] [3, 1, 2] FalsePython tiene una convención: las funciones y métodos que modifican en el sitio devuelven None. list.sort(), list.append(), list.reverse(), dict.update() y random.shuffle() lo hacen todos. Es un acuerdo para no ocultar que lo que ha cambiado es el original.
De ahí sale este error típico:
nums = [3, 1, 2]
nums = nums.sort()
print(nums)
# NoneModifica en el sitio (devuelve None) | Devuelve un objeto nuevo |
|---|---|
lst.sort() | sorted(lst) |
lst.reverse() | lst[::-1], list(reversed(lst)) |
lst.append(x), lst.extend(xs) | lst + [x] |
d.update(other) | d | other |
Cuando escribas tus propias funciones, elige una de las dos. Si modificas y además devuelves, quien llama puede usar el resultado sin darse cuenta de que el original ha cambiado.
*args y **kwargs, y la copia defensiva (7 min)*args reúne los argumentos posicionales sobrantes en una tupla nueva, y **kwargs reúne los argumentos por palabra clave sobrantes en un dict nuevo. Pero lo que llevan dentro son etiquetas de los objetos que pasaste.
def g(*args, **kwargs):
return args, kwargs
items = ["a"]
opts = {"tag": items}
a, k = g(items, **opts)
print(type(a), type(k))
# <class 'tuple'> <class 'dict'>
print(a[0] is items, k is opts, k["tag"] is items)
# True False TrueAunque desempaquetes con **opts, el kwargs que recibe la función es un dict nuevo. Por eso, si la función añade una clave a kwargs, opts no cambia. Pero la lista que había como valor es el mismo objeto.
Tablero de etiquetas
Paso 1 / 0
Lo siguiente que se ejecuta: 0
Preparando el laboratorio…
Acaba de cambiar
Maleta sellada (inmutable)
Hay que tener especial cuidado con las funciones que guardan durante mucho tiempo un objeto mutable recibido. Si quien llama modifica ese objeto más tarde, lo guardado cambia también.
def make_flight(code, passengers):
return {"code": code, "passengers": passengers}
names = ["Ari", "Bo"]
f1 = make_flight("HS1", names)
names.append("Cy")
print(f1["passengers"])
# ['Ari', 'Bo', 'Cy']En ese caso, la función crea su propia copia en el momento de recibirlo. Esto se llama copia defensiva (defensive copy).
def make_flight(code, passengers):
return {"code": code, "passengers": list(passengers)}
names = ["Ari", "Bo"]
f1 = make_flight("HS1", names)
names.append("Cy")
print(f1["passengers"])
# ['Ari', 'Bo']list(passengers) es una copia superficial (lección 3). Como dentro solo hay cadenas, con eso basta. En cambio, si la función solo lee un momento lo que recibe, no hace falta copiarlo.
return tampoco copia el objeto. Entrega a quien llama la maleta creada dentro de la función, y el nombre del lado que llama le cuelga una etiqueta. Aunque la casilla de la función desaparezca, la maleta sigue ahí porque tiene una etiqueta fuera.
def build():
bag = ["boarding pass"]
return bag
b = build()
print(b)
# ['boarding pass']return a, b, que parece devolver varios valores, en realidad devuelve una sola tupla. Si quien la recibe la desempaqueta, cada valor recibe su etiqueta.
def stats(xs):
return min(xs), max(xs)
res = stats([4, 9, 2])
print(res, type(res))
# (2, 9) <class 'tuple'>
lo, hi = stats([4, 9, 2])
print(lo, hi)
# 2 9Una función sin return devuelve None. Por eso las «funciones que modifican» de 4-3 devuelven None.
Esta es la función del parte, y la función corregida.
def new_order(item, cart=[]):
cart.append(item)
return cart
print(new_order("coffee"))
# ['coffee']
print(new_order("bagel"))
# ['coffee', 'bagel']
print(new_order.__defaults__)
# (['coffee', 'bagel'],)
def new_order(item, cart=None):
if cart is None:
cart = []
cart.append(item)
return cart
print(new_order("coffee"), new_order("bagel"))
# ['coffee'] ['bagel']Los pedidos anteriores estaban en la lista colgada de __defaults__ del objeto función. Como el servidor define la función una vez y la llama una y otra vez, esa lista fue acumulando los pedidos de cada cliente hasta que se apagara el servidor.
Cuando la desarrolladora se va, la supervisora añade: «Las etiquetas se caen cuando la función termina, pero la maleta que sujeta el objeto función no se cae. La función también es una maleta.»
Informe de cierre — LOST-004. El argumento por defecto cart=[] se creó una vez en el def y se quedó en __defaults__. Todas las llamadas compartían la misma lista. Resuelto con el centinela None, que crea una lista nueva en cada llamada.
def add(x, seen=set()) hace seen.add(x) y devuelve len(seen), predice el resultado de add(1), add(2) y add(1), y luego mira add.__defaults__.def clear(lst): lst = [] y qué efecto tiene def clear(lst): lst.clear()?sorted y copiando y luego usando sort) una función que reciba una lista y devuelva una lista nueva ordenada sin tocar el original.La libreta de la supervisora
Los argumentos se pasan por referencia a objeto. El parámetro es una etiqueta más colgada del mismo objeto.
Si dentro de la función modificas el objeto, se ve fuera; si reasignas, no.
Los argumentos por defecto se evalúan una vez en el def y se quedan en __defaults__. En vez de un valor por defecto mutable, None.
Las funciones que modifican en el sitio devuelven None (sort frente a sorted).
*args y **kwargs son una tupla y un dict nuevos, pero los objetos de dentro son los mismos. Lo que vayas a guardar mucho tiempo, con copia defensiva.
return también mueve etiquetas. Devolver varios valores es devolver una tupla.
← Anterior
Lección 3 · Maletas dentro de maletas: contenedores y copias
Siguiente →
Lección 5 · Zonas y pases: ámbitos
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.