Inscríbete para acceder a todas las lecciones de este curso.
Lección 2 de 29· Empieza aquí: leer código como quien revisa
Esta es una función que podrías encontrarte hoy mismo en un pull request, en el código de un compañero o en la respuesta de un asistente de IA. Calcula el valor promedio de una lista de importes de pedidos de una tienda online:
def promedio_de_pedidos(valores):
total = 0
for v in valores:
total += v
return total / len(valores)Seis líneas. Nombres claros. Sin trucos raros. Se lee en dos segundos y el cerebro dice "esto está bien".
Y está mal.
No mal de "no compila" ni de "le falta un paréntesis": esos fallos los caza el intérprete por ti. Está mal de la forma que de verdad duele — corre perfecto en las pruebas que hiciste y revienta el martes a las 3 de la tarde en producción. Ese es exactamente el tipo de fallo que este curso te enseña a ver, y vas a cazar el primero ahora mismo.
Esta es la técnica central del curso y la vas a usar en cada lección: antes de correr nada, escribe qué crees que va a pasar. No lo pienses "por encima" — comprométete con una respuesta concreta. Si aciertas, tu modelo mental del código es correcto. Si fallas, acabas de descubrir un hueco en tu modelo mental… o un bug en el código. Ambas cosas valen oro.
Toma dos entradas y responde mentalmente (o en un papel):
| # | Llamada | ¿Qué imprime print(...)? |
|---|---|---|
| A | promedio_de_pedidos([189.9, 149.0, 79.5]) | ¿…? |
| B | promedio_de_pedidos([]) | ¿…? |
El caso A es el camino feliz: tres pedidos, suma y divide. Sale un número y no hay sorpresa.
El caso B es el que separa a quien lee código de quien lo revisa. Una lista vacía no es un caso exótico inventado para el examen: es lo que te devuelve una consulta de "pedidos de hoy" a las 00:05, un filtro por una categoría sin ventas, o un cliente nuevo sin historial. La lista vacía es el caso borde más común del universo.
Comprométete con una respuesta para B antes de seguir. ¿0? ¿0.0? ¿None? ¿Un error? ¿Cuál?
Ahora contrasta tu predicción con la realidad. En la celda de abajo ya está la función tal cual, más una llamada al caso A. Lo único que falta es provocar el caso B dentro del try para ver qué ocurre de verdad.
Es un cambio de una sola línea. Cámbialo, pulsa Ejecutar y luego Comprobar.
Reemplaza la línea pass por una llamada a promedio_de_pedidos con una lista vacía, de modo que el bloque except se active. No cambies nada más: la salida debe ser exactamente el promedio del caso A (redondeado a 2 decimales) y luego la línea que revela el fallo.
def promedio_de_pedidos(valores):
total = 0
for v in valores:
total += v
return total / len(valores)
# Caso A: el camino feliz (esto ya funciona)
print(round(promedio_de_pedidos([189.9, 149.0, 79.5]), 2))
# Caso B: la lista vacía. ¿Qué pasa aquí?
try:
pass # TODO: reemplaza esta línea por la llamada con una lista vacía
except ZeroDivisionError as e:
print("Falla con lista vacia ->", type(e).__name__)| Caso | Predicción típica | Realidad |
|---|---|---|
A: [189.9, 149.0, 79.5] | 139.47 | 139.47 ✅ |
B: [] | "devolverá 0" | ZeroDivisionError 💥 |
Casi todo el mundo predice que el caso B devuelve 0. Es lo que dice la intuición: "no hay pedidos, el promedio es cero". Pero Python no comparte tu intuición: ejecuta lo que está escrito, y lo que está escrito es una división entre len(valores), que con la lista vacía vale 0.
La definición matemática del promedio ya te avisaba:
La fórmula no está definida para . El código la tradujo literalmente y heredó el agujero. Un revisor con criterio lee / len(valores) y automáticamente se pregunta: ¿y si eso vale cero? Esa pregunta refleja — "¿puede el denominador ser cero?", "¿puede la lista venir vacía?", "¿puede este valor ser None?" — es literalmente el músculo que vas a entrenar durante todo el curso.
Cuando pides "una función que calcule el promedio de una lista", tanto una persona con prisa como un modelo de lenguaje producen el caso feliz, bien escrito. El código sale limpio, con nombres razonables y sin errores de sintaxis: es plausible. Y ahí está la trampa — revisamos el código buscando cosas feas, y este código no es feo. Lo que le falta no se ve: es una rama que nadie escribió.
Regla que te llevas: los bugs peligrosos no están en lo que el código hace mal, sino en los casos que el código no contempla. Revisar no es leer las líneas que hay; es preguntarse por las que faltan.
None que se cuelaAntes de arreglarlo, mira una segunda entrada realista. En el dataset de pedidos de una tienda hay filas donde la cantidad o el importe simplemente no llegó (un registro incompleto, una columna vacía en el CSV). En Python eso suele viajar como None:
promedio_de_pedidos([189.9, None, 149.0])
# TypeError: unsupported operand type(s) for +=: 'int' and 'NoneType'Misma función, mismo origen: el autor asumió una entrada ideal (una lista no vacía de números limpios). En cuanto la realidad se aparta un milímetro de esa suposición, la función se rompe.
Fíjate en la diferencia de gravedad entre los dos fallos: aquí explota ruidosamente, y un error ruidoso es un error afortunado — lo ves en el traceback y lo arreglas. Los verdaderamente peligrosos son los silenciosos: los que devuelven un número equivocado pero creíble y acaban en un informe. A esos (tipos corrompidos en silencio, off-by-one, agregaciones mal hechas) les dedicamos módulos enteros más adelante.
Cazar el bug es la mitad del trabajo; la otra mitad es corregirlo sin romper lo que funcionaba. Vamos con el caso vacío, que es el que acabas de reproducir.
La decisión de diseño: ¿qué debería devolver el promedio de una lista vacía? Hay tres respuestas defendibles, y la elección se documenta, no se improvisa:
| Opción | Cuándo tiene sentido |
|---|---|
Devolver 0.0 | Cuando "sin pedidos" equivale a "cero" en el informe y quieres que nada se rompa aguas abajo. |
Devolver None | Cuando "no hay dato" es distinto de "el dato es cero" y quien llama debe decidir qué mostrar. |
Lanzar ValueError con un mensaje claro | Cuando recibir una lista vacía indica un error del llamador que conviene detectar pronto. |
Para este ejercicio la especificación es la primera: con lista vacía, la función devuelve 0.0 (el flotante, no el entero 0). Y el camino feliz debe seguir dando exactamente el mismo resultado que antes.
Completa la función para que la celda imprima las tres líneas esperadas.
Corrige promedio_de_pedidos para que devuelva 0.0 cuando reciba una lista vacía, sin alterar el resultado de los demás casos. No cambies las tres líneas de print: la salida debe coincidir exactamente con la esperada.
def promedio_de_pedidos(valores):
# TODO: contempla el caso de la lista vacía antes de dividir.
# Debe devolver 0.0 (flotante) y no lanzar ninguna excepción.
total = 0
for v in valores:
total += v
return total / len(valores)
print(round(promedio_de_pedidos([189.9, 149.0, 79.5]), 2))
print(promedio_de_pedidos([]))
print(round(promedio_de_pedidos([3890.0]), 2))Acabas de hacer, en pequeño, el ciclo completo de una revisión profesional:
Ese último punto no es un detalle: los tres print de la celda final son, de hecho, tus primeras pruebas de regresión. En el módulo sobre tests los convertiremos en pytest de verdad y verás por qué un test verde no demuestra nada si nunca toca el caso borde.
None?".En la siguiente lección conviertes esto en un método repetible: cómo leer código desconocido de forma activa, qué preguntas hacerle línea a línea y cómo entrenar la predicción hasta que sea automática.
Antes de seguir, fija los términos clave con estas tarjetas.
Gratis