Ir al contenido
wannacode

Excepciones

En resumen: Una excepción es un objeto que describe un error ocurrido mientras el programa se ejecuta: el traceback muestra dónde, y try y except lo atrapan para que el programa pueda seguir.

Problema

12a en lugar de 12

La taquilla del concierto pregunta cuántas entradas quieres. Una entrada cuesta 15 €.

tickets.py
tickets = int(input("¿Cuántas entradas? "))
print(f"Total: {tickets * 15} €")

Con la entrada 12 el programa dice «Total: 180 €». Pero el cliente tiene prisa y pulsa una letra de más:

La consola
¿Cuántas entradas? 12a
Traceback (most recent call last):
File "/projects/tickets/tickets.py", line 1, in <module>
tickets = int(input("¿Cuántas entradas? "))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ValueError: invalid literal for int() with base 10: '12a'

El programa se detiene. En lugar del total sale un texto largo, y la línea «Total» nunca se alcanza. ¿Qué significa este texto, y cómo evitas que el programa se detenga por una letra?

La idea

Excepción

int() tiene que devolver un número. No puede sacar un número de «12a», y devolver un número cualquiera sería mentir. Así que la función crea un objeto que describe el problema y lo lanza. Este objeto se llama excepción.

En Python la palabra es raise, pero el significado es el mismo. Si nadie la atrapa, el programa se detiene y muestra qué pasó. Este texto se llama traceback, y se lee de abajo arriba, porque lo más reciente está abajo (most recent call last):

  • La última línea: el tipo de excepción y el mensaje. ValueError: invalid literal for int() with base 10: '12a': un valor incorrecto, y aquí está el texto.
  • Las líneas «File» de encima: dónde ocurrió. Es la pila de llamadas de la lección de recursión, pero de fuera hacia dentro: arriba está el lugar más externo, y abajo está la función que lanzó. <module> significa el código del nivel superior del archivo.
  • Tu línea: la última línea «File» que apunta a tu archivo. Aquí: tickets.py, line 1. Debajo está la propia línea, y las marcas ^^^ muestran la parte que lanzó.

Los mensajes están en inglés porque los escribe el lenguaje, no tú.

Pruébalo

Encuentra el error

El programa debería imprimir la última nota del examen. Se detiene con esto:

Error
Traceback (most recent call last):
File "/projects/tickets/scores.py", line 6, in <module>
print(last_score(scores))
^^^^^^^^^^^^^^^^^^
File "/projects/tickets/scores.py", line 2, in last_score
return scores[len(scores)]
~~~~~~^^^^^^^^^^^^^
IndexError: list index out of range

Lee el traceback. ¿En qué línea del programa está el error?

Elige la línea con el error.

La idea

try y except

Una excepción se puede atrapar. El código que podría lanzar va en try. Lo que debe pasar cuando hay un error, lo escribes en except (como en «excepto si pasa esto»):

text = input("¿Cuántas entradas? ")
try:
tickets = int(text)
print(f"Total: {tickets * 15} €")
except ValueError:
print("Eso no es un número entero.")
print("¡Gracias!")
Entrada
12a
Salida
¿Cuántas entradas? Eso no es un número entero.
¡Gracias!
  • Después de except escribes qué tipo de excepción atrapas, aquí ValueError.
  • Si todo en try sale bien, except se salta. Con la entrada 12 la salida es «Total: 180 €» y «¡Gracias!».
  • Después de except el programa sigue adelante. Ya no se detiene.

text = input("¿Cuántas entradas? ")
try:
tickets = int(text)
print(f"Total: {tickets * 15} €")
except ValueError:
print("Eso no es un número entero.")
print("¡Gracias!")
Paso a paso, en palabras
  1. El programa pregunta cuántas entradas. Escribes «12a».
  2. En try: int() intenta sacar un número de «12a».
  3. No puede. La función lanza ValueError.
  4. La ejecución salta directamente a except. La línea con «Total» se salta.
  5. except es justo para este tipo de error: imprime el mensaje para el cliente.
  6. Después de except el programa sigue. No se detiene.
Más precisamente, para curiosos
  • En Python una variable creada en try también existe después: los bloques no tienen su propio ámbito. Pero si int() ha lanzado, tickets nunca se crea, y print(tickets) después del bloque se detiene con NameError.
try:
tickets = int("12")
except ValueError:
print("Eso no es un número entero.")
print(tickets)
Salida
12
  • El nombre de la excepción es opcional: except ValueError:. Si lo necesitas, escribes except ValueError as e:.

Pruébalo

¿Qué líneas se ejecutan?

¿Qué imprimirá el programa?

print("Inicio")
try:
print("Leo")
seats = int("diez")
print(f"Leí {seats}")
except ValueError:
print("Entrada incorrecta")
print("Fin")
Respuesta

La idea

Qué dice la excepción

La excepción es un objeto, y e en except ValueError as e: es la variable que apunta a él. El mensaje es el texto del propio objeto: str(e), o simplemente {e} en un f-string.

try:
tickets = int(input())
print(f"Total: {tickets * 15} €")
except ValueError as e:
print(f"Error: {e}")
Entrada
12a
Salida
Error: invalid literal for int() with base 10: '12a'

El mensaje te sirve a ti cuando buscas el error. Al cliente normalmente le muestras tu propio texto, en su idioma, como «Eso no es un número entero.»

La idea

Pregunta hasta que esté bien

El cliente se equivocó. No tienes que detenerte. Pregunta otra vez:

while True:
try:
tickets = int(input("¿Cuántas entradas? "))
break
except ValueError:
print("Escribe un número entero, por ejemplo 3.")
print(f"Total: {tickets * 15} €")
Entrada
12a
doce
12
Salida
¿Cuántas entradas? Escribe un número entero, por ejemplo 3.
¿Cuántas entradas? Escribe un número entero, por ejemplo 3.
¿Cuántas entradas? Total: 180 €

while True sigue dando vueltas hasta que break lo detiene. Y a break solo se llega si int() sale bien. Con un error, la ejecución salta a except, y después el bucle vuelve a empezar y pregunta otra vez.

Más precisamente, para curiosos
  • Python no tiene TryParse. Atrapar ValueError es la manera habitual: «inténtalo y atrápalo» es una costumbre en Python. Existe text.isdigit(), pero también dice False para "-3", así que no sustituye a int():
print("12".isdigit())
print("12a".isdigit())
print("-3".isdigit())
Salida
True
False
False

Pruébalo

División entre cero

No toda excepción viene de la entrada. El valor medio de un pedido es la facturación dividida entre el número de pedidos. Pero el primer día no hay pedidos. Con la división entera, revenue // orders detiene el programa:

Error
ZeroDivisionError: integer division or modulo by zero

¿Y con la división normal /, que da un número decimal?

¿Qué imprimirá el programa?

revenue = 90
orders = 0
average = revenue / orders
print(average)
Respuesta
Más precisamente, para curiosos
  • Los números decimales de Python siguen el estándar IEEE 754 y tienen el infinito y nan, pero dividir entre cero no los da: Python elige lanzar. El infinito solo lo obtienes si lo pides:
print(float("inf"))
print(float("inf") - float("inf"))
Salida
inf
nan

Ejercicio

Programas que no se detienen

Resuélvelas en tu editor. No imprimas mensajes para pedir datos, para que tu salida coincida con el ejemplo.

1. Texto a número. Escribe una función to_number(text) que devuelva el texto como float. Lee dos líneas. Para cada una, imprime el número o un mensaje de que no es un número. Atrapa la excepción donde llamas a la función.

Entrada
123.45
123.abc
Salida
El número es: 123.45
Error: 123.abc no es un número.

2. No hay tal producto. Los precios están en la lista [120, 75, 300, 45, 89]. Lee un número de producto del 1 al 5 e imprime su precio. Si el número está fuera de la lista, atrapa la excepción y dilo. Con la entrada 2 la salida es Precio: 75 €.

Entrada
7
Salida
No hay ningún producto con el número 7.

Cuidado con el número 0: en Python prices[-1] no lanza. Es el último precio, 89 €. Un índice negativo cuenta desde el final. Así que para 0, añade también un if.

3. Pedido medio. Lee la facturación y el número de pedidos (números enteros) e imprime la media con división entera. Si no hay pedidos, atrapa la excepción. Con la entrada 900 y 4 la salida es Media: 225 €.

Entrada
90
0
Salida
Todavía no hay pedidos.

Más tareas

  • Edad, hasta que esté bien. Pregunta por una edad hasta que la entrada sea un número entero del 0 al 120. Para un texto como «veinte» di «Eso no es un número.», y para 150 di «La edad tiene que ser del 0 al 120.» Lo uno es una excepción, lo otro es un if.
  • Suma de los válidos. Lee 5 líneas. Suma las que son números enteros y cuenta las que se saltan: con la entrada 10, abc, 5, 7.5, 3 la salida es Suma: 18, saltadas: 2.
  • División a petición. Lee dos números enteros e imprime el cociente. Con texto en lugar de un número y con división entre cero, el programa no se detiene. Vuelve a pedir los dos números.

Test

Ponte a prueba

1. ¿Qué es una excepción?
2. El programa se detuvo con un Traceback. ¿Por dónde empiezas a leer?
3. Un try tiene cuatro líneas. La segunda lanza una excepción, y except la atrapa. ¿Qué pasa con la tercera y la cuarta?
4. ¿Qué pasa con average = revenue / orders cuando orders es 0?
5. ¿Atrapa except ValueError: el error de scores[10] en una lista de 5 elementos?

Creado por Ivo Gergov como parte de la campaña de Evo Tech por una educación accesible en programación ·