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 4

Tiempo 2:30–3:20

Al entregar una maleta: funciones y argumentos

Parte de equipaje perdido

LOST-004

En cada llamada vuelven los pedidos anteriores

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.»

4-1. La función cuelga una etiqueta más de la maleta (11 min)

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)
# True

Como solo se ha colgado una etiqueta más, se aplican tal cual las dos operaciones de la lección 1.

  • Cambiar la maleta: si dentro de la función modificas el contenido, como con bag.append(...), el cambio se ve también desde el nombre de fuera. Es la misma maleta.
  • Mover la etiqueta: si dentro de la función reasignas con 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)

Con bag.append, el bag de la casilla pack y el mine de module apuntan a la misma maleta, y esa maleta cambia. Con bag = bag + [...], solo bag se muda a una maleta nueva, y cuando la casilla desaparece esa maleta nueva se tira también.

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.

4-2. Los argumentos por defecto se crean una sola vez, al definir la función (11 min)

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])
# True

Sigue 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)

Con orders=[], la tupla defaults de la maleta function sujeta una lista, y las dos llamadas meten cosas en la misma lista. Con orders=None, en defaults solo hay None, y cada llamada crea una lista nueva.

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
  • No solo son peligrosos [], {} 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.
  • Los valores por defecto de los argumentos solo por palabra clave se cuelgan aparte, en f.__kwdefaults__.

4-3. Funciones que modifican y funciones que devuelven algo nuevo (7 min)

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] False

Python 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)
# None
Modifica 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.

4-4. *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 True

Aunque 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)

En la casilla pack, items es una tupla nueva y labels es un dict nuevo. La primera casilla de la tupla apunta a la misma maleta que el bag de fuera, así que el append de la línea 2 se ve también desde fuera.

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.

4-5. Devolver un valor también es mover etiquetas (4 min)

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 9

Una función sin return devuelve None. Por eso las «funciones que modifican» de 4-3 devuelven None.

Caso cerrado (10 min)

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.

Ejercicios

  1. Si 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__.
  2. ¿Qué efecto tiene en la lista de fuera def clear(lst): lst = [] y qué efecto tiene def clear(lst): lst.clear()?
  3. Escribe de dos formas (con 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.

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