Ir al contenido
wannacode

try, except y finally

En resumen: Con varios bloques except atrapas tipos distintos de excepciones, de lo concreto a lo general, y finally se ejecuta siempre, con error o sin él.

Problema

La cuenta para cero personas

El programa divide la cuenta del restaurante a partes iguales. Ya conoces la entrada incorrecta, y la atrapas:

bill.py
try:
bill = int(input())
people = int(input())
print(f"Por persona: {bill // people} €")
except ValueError:
print("Escribe números enteros.")

La cuenta es de 90 €, y alguien escribe 0 como número de personas. El programa vuelve a detenerse:

Error
Traceback (most recent call last):
File "/projects/bill/bill.py", line 4, in <module>
print(f"Por persona: {bill // people} €")
~~~~~^^~~~~~~~
ZeroDivisionError: integer division or modulo by zero

except está ahí, pero no ayuda. Solo atrapa ValueError, y esta es otra clase de excepción.

La idea

Varios bloques except

Después de un try pueden ir varios bloques except, uno para cada tipo de error:

try:
bill = int(input())
people = int(input())
print(f"Por persona: {bill // people} €")
except ValueError:
print("Escribe números enteros.")
except ZeroDivisionError:
print("Las personas no pueden ser 0.")
Entrada Salida
90 y 3 Por persona: 30 €
90 y 0 Las personas no pueden ser 0.
90 y tres Escribe números enteros.

Los bloques se comprueban de arriba abajo. Se ejecuta el primero cuyo tipo coincide, y solo ese. Los demás se saltan.

try:
bill = int(input())
people = int(input())
print(f"Por persona: {bill // people} €")
except ValueError:
print("Escribe números enteros.")
except ZeroDivisionError:
print("Las personas no pueden ser 0.")
Paso a paso, en palabras
  1. Primera ejecución: cuenta de 90 €, las personas son 0.
  2. 90 / 0 es una división entre cero: se lanza ZeroDivisionError.
  3. El primer except es para ValueError. No coincide, se salta.
  4. El segundo coincide. Se ejecuta él y solo él.
  5. Segunda ejecución: en lugar del número de personas se ha escrito «tres».
  6. int() no puede sacar un número: se lanza ValueError.
  7. Ya coincide el primer except. Al segundo no se llega.
  8. Los bloques se comprueban de arriba abajo. Se ejecuta el primero que coincide.
Más precisamente, para curiosos
  • Cuando dos tipos se tratan igual, puedes atraparlos en un solo bloque con una tupla: except (ValueError, ZeroDivisionError):.

La idea

La familia de las excepciones

Las excepciones son clases, relacionadas por herencia. Aquí están las más comunes:

La jerarquía
BaseException
├── KeyboardInterrupt Ctrl+C, no se atrapa
├── SystemExit sys.exit(), no se atrapa
└── Exception
├── ArithmeticError
│ ├── ZeroDivisionError
│ └── OverflowError
├── LookupError
│ ├── IndexError
│ └── KeyError
├── ValueError
├── TypeError
└── OSError archivos, red
└── FileNotFoundError

except atrapa el tipo que nombras y todas sus clases hijas. Es el polimorfismo del módulo sobre la herencia: un objeto de una clase hija también es del tipo de la clase padre. issubclass lo muestra:

print(issubclass(ZeroDivisionError, ArithmeticError))
print(issubclass(IndexError, LookupError))
print(issubclass(ValueError, Exception))
print(issubclass(KeyboardInterrupt, Exception))
Salida
True
True
True
False

Por eso except Exception as e: lo atrapa casi todo:

def split(bill_text, people_text):
try:
bill = int(bill_text)
people = int(people_text)
print(f"Por persona: {bill // people} €")
except Exception as e:
print(f"{type(e).__name__}: {e}")
split("90", "3")
split("90", "0")
split("90", "tres")
Salida
Por persona: 30 €
ZeroDivisionError: integer division or modulo by zero
ValueError: invalid literal for int() with base 10: 'tres'

type(e).__name__ da el nombre de la clase real del objeto. El except es para Exception, pero el objeto es de una clase hija.

Más precisamente, para curiosos
  • KeyboardInterrupt (Ctrl+C) y SystemExit (sys.exit()) no son clases hijas de Exception, sino de BaseException. Así except Exception: no impide que se detenga el programa. except: sin tipo y except BaseException: también los atrapan. No los escribas.
  • Atrapa de la forma más concreta que puedas. except Exception: también esconde errores que no esperabas, como un fallo en tu propio código.

Pruébalo

¿Qué catch?

¿Qué imprimirá el programa?

seats = [4, 2, 6]
try:
print(f"Mesa 4: {seats[3]} sitios")
except ZeroDivisionError:
print("División entre cero")
except LookupError:
print("Error de búsqueda")
except Exception:
print("Error general")
Respuesta

Pruébalo

Encuentra el error

Con text = "0" el programa imprime «Algo salió mal.» y no «Las personas no pueden ser 0.». ¿En qué línea está el error?

Elige la línea con el error.

La idea

Nadie te obliga

Java tiene excepciones comprobadas: el compilador no acepta el programa hasta que las atrapas o las declaras con throws. Python no tiene ninguna así. Ninguna excepción tiene que atraparse:

import time
def print_receipt():
print("Imprimo el recibo...")
time.sleep(0.3)
print("Listo.")
print_receipt()
Salida
Imprimo el recibo...
Listo.

import time trae el módulo del tiempo, y time.sleep(0.3) pausa el programa 0,3 segundos. Si pulsas Ctrl+C durante la pausa, Python lanza KeyboardInterrupt, pero nadie te pide que la atrapes. Lo mismo pasa con int() y ValueError.

Así que la responsabilidad es tuya. Qué excepciones lanza una función está escrito en su documentación, normalmente con la palabra «Raises». Léela antes de decidir si atrapar.

La idea

finally: siempre

La impresora de la caja tiene que liberarse después de cada recibo, o el siguiente no se imprimirá. Pase lo que pase, salga bien o haya un error:

def print_receipt(qty_text):
print("La impresora está ocupada.")
try:
qty = int(qty_text)
print(f"Café: {qty} x 2 € = {qty * 2} €")
except ValueError:
print(f"Cantidad incorrecta: {qty_text}")
finally:
print("La impresora está libre.")
print_receipt("3")
print_receipt("tres")
Salida
La impresora está ocupada.
Café: 3 x 2 € = 6 €
La impresora está libre.
La impresora está ocupada.
Cantidad incorrecta: tres
La impresora está libre.

finally se ejecuta siempre: cuando todo sale bien, cuando la excepción se atrapa, e incluso cuando ninguno de estos bloques except la atrapa. En este último caso finally se ejecuta, y la excepción sigue subiendo hasta el siguiente try que pueda atraparla.

¿Por qué no una simple línea después del try/except? Porque esa línea se salta si la excepción es de un tipo que except no atrapa, o si try tiene un return. finally no se salta.

Python también tiene else: se ejecuta solo si no se lanzó ninguna excepción en try. Así try contiene solo la línea que puede lanzar, y la impresión va después:

def print_receipt(qty_text):
print("La impresora está ocupada.")
try:
qty = int(qty_text)
except ValueError:
print(f"Cantidad incorrecta: {qty_text}")
else:
print(f"Café: {qty} x 2 € = {qty * 2} €")
finally:
print("La impresora está libre.")
print_receipt("3")
print_receipt("tres")
Salida
La impresora está ocupada.
Café: 3 x 2 € = 6 €
La impresora está libre.
La impresora está ocupada.
Cantidad incorrecta: tres
La impresora está libre.
Más precisamente, para curiosos
  • También puedes tener try con finally y sin except: la excepción no se atrapa, pero la limpieza se ejecuta.
  • Para los recursos que hay que cerrar, como archivos y conexiones a bases de datos, hay una forma más corta: with. La veremos cuando lleguemos a los archivos. Cierra el recurso por sí sola, igual que lo haría finally.
  • finally se ejecuta incluso con sys.exit(): en Python eso también es una excepción (SystemExit), y sube pasando por finally.

Pruébalo

finally y return

¿Qué imprimirá el programa?

def reserve(guests):
try:
if guests > 6:
return f"No hay mesa para {guests}"
return f"Mesa para {guests}"
finally:
print("Registré el intento.")
print(reserve(8))
Respuesta

Ejercicio

Programas con varios bloques except

Resuélvelas en tu editor. No imprimas mensajes para pedir datos.

1. Precio por persona. Los precios están en la lista [120, 75, 300]. Lee un número de producto (desde 1) y el número de personas, e imprime el precio por persona con división entera. Atrapa tres cosas: un producto que no existe, cero personas, y todo lo demás (entonces imprime el mensaje de la excepción).

Entrada Salida
2 y 3 Por persona: 25 €
5 y 2 No existe ese producto.
1 y 0 El número de personas no puede ser 0.
1 y dos Error: invalid literal for int() with base 10: 'dos'

2. Calculadora. Lee un número entero, un operador (+, -, * o /) y un segundo número entero, cada uno en su propia línea. Imprime la cuenta (la división es //). Con texto en lugar de un número di «Escribe números enteros.», con división entre cero «División entre cero.», y con cualquier otro operador «Operador desconocido: » seguido del operador. Para el operador viene bien un match.

Entrada
84
/
4
Salida
84 / 4 = 21
Entrada
84
/
0
Salida
División entre cero.

Más tareas

  • El datáfono. La función pay(amount_text) imprime «El datáfono está conectado.», después «Pagado: X €» o un mensaje sobre la entrada incorrecta, y en finally «El datáfono está apagado.». Llámalo con «25» y con «veinticinco».
  • Nota media sin caídas. Lee 5 líneas con notas. Sáltate las que no son números enteros (una excepción) o no están entre 2 y 6 (un if). Imprime la media con división entera. Si ninguna nota es válida, la división entre cero lanza: atrápala e imprime «No hay notas válidas.»
  • Predice y comprueba. Escribe un try con except y finally, donde cada bloque imprime una línea. Predice la salida cuando sale bien, con una excepción atrapada y con una no atrapada, y después ejecútalo.

Test

Ponte a prueba

1. La excepción coincide con dos de los tres bloques except. ¿Cuál se ejecuta?
2. ¿Atrapa except ArithmeticError: una excepción de tipo ZeroDivisionError?
3. ¿Por qué except Exception: tiene que ir el último?
4. ¿Cuándo se ejecuta finally?
5. ¿Cuándo se ejecuta else después de try/except?
6. ¿Te obliga Python a atrapar ValueError alrededor de int()?

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