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:
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:
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 zeroexcept 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
- Primera ejecución: cuenta de 90 €, las personas son 0.
- 90 / 0 es una división entre cero: se lanza ZeroDivisionError.
- El primer except es para ValueError. No coincide, se salta.
- El segundo coincide. Se ejecuta él y solo él.
- Segunda ejecución: en lugar del número de personas se ha escrito «tres».
- int() no puede sacar un número: se lanza ValueError.
- Ya coincide el primer except. Al segundo no se llega.
- 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:
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 └── FileNotFoundErrorexcept 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))TrueTrueTrueFalsePor 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")Por persona: 30 €ZeroDivisionError: integer division or modulo by zeroValueError: 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) ySystemExit(sys.exit()) no son clases hijas deException, sino deBaseException. Asíexcept Exception:no impide que se detenga el programa.except:sin tipo yexcept 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")seats[3] está fuera de la lista de 3 elementos: IndexError. No es un ZeroDivisionError, así que el primer bloque se salta. Es una clase hija de LookupError, así que el segundo coincide y se ejecuta. Al tercero nunca se llega, aunque también coincidiría.Pruébalo
Encuentra el error
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.
Exception es la clase padre de ZeroDivisionError. El primer except lo atrapa todo, y al segundo nunca se puede llegar. Python no dice nada: el programa se ejecuta y simplemente imprime el mensaje general. La regla es de lo concreto a lo general: intercambia los dos bloques.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()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")La impresora está ocupada.Café: 3 x 2 € = 6 €La impresora está libre.La impresora está ocupada.Cantidad incorrecta: tresLa 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")La impresora está ocupada.Café: 3 x 2 € = 6 €La impresora está libre.La impresora está ocupada.Cantidad incorrecta: tresLa impresora está libre.Más precisamente, para curiosos
- También puedes tener
tryconfinallyy sinexcept: 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íafinally.
finallyse ejecuta incluso consys.exit(): en Python eso también es una excepción (SystemExit), y sube pasando porfinally.
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))Un return en try no se salta finally. La función ejecuta primero finally, y después devuelve el texto. Por eso «Registré el intento.» se imprime antes del resultado, que print recibe solo cuando la función ha terminado.
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.
84/484 / 4 = 2184/0Divisió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 enfinally«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
tryconexceptyfinally, 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
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Métodos de instancia · Herencia · Polimorfismo · Excepciones
- Continúa en: Lanzar excepciones y crear las tuyas · Archivos · Datos en archivos · Códigos hash y funciones hash
- Ejercicios sobre el tema: Errores y excepciones