Excepciones en Python — try, except, raise y assert sin rodeos
Las excepciones en Python son el mecanismo que permite a un programa gestionar situaciones inesperadas sin romperse. En FP1 aprendiste a prevenir errores con if/else. En FP2 das un paso más, aprendes a capturarlos cuando ocurren y a lanzarlos tú mismo cuando es necesario.
En este artículo vemos todo: try/except, excepciones específicas y genéricas, finally, raise y assert.
Tabla de Contenidos
¿Qué son las excepciones en Python?
Cuando Python encuentra una situación que no puede manejar, dividir entre cero, convertir texto que no es número, acceder a un índice que no existe, lanza una excepción y el programa se detiene. Una excepción es un objeto que representa ese error.
a = int(input('a: '))
b = int(input('b: '))
print(a // b)
a: 7 b: 0 ZeroDivisionError: integer division or modulo by zero
El programa muestra el tipo de excepción (ZeroDivisionError) y termina. Sin gestión de excepciones, cualquier error inesperado mata el programa.
try/except vs if/else — cuándo usar cada uno
La pregunta que más aparece en FP2: ¿cuándo uso if/else y cuándo uso try/except?
# CON if/else — prevención
b = int(input('b: '))
if b != 0:
print(a // b)
else:
print('No se puede dividir entre cero')
# CON try/except — gestión
try:
b = int(input('b: '))
print(a // b)
except ZeroDivisionError:
print('No se puede dividir entre cero')
La regla práctica es clara:
Usa if/else cuando puedes comprobar la condición antes de que ocurra el error y la comprobación es barata, como comprobar si un número es cero antes de dividir.
Usa try/except cuando no puedes saber de antemano si va a ocurrir un error, como intentar abrir un fichero, convertir entrada del usuario, o acceder a recursos externos. También cuando el código que puede fallar es complejo y envolverlo en condiciones lo haría ilegible.
En Python hay además una filosofía conocida como EAFP (Easier to Ask Forgiveness than Permission): es más pythónico intentar la operación y capturar el error que comprobar antes si es posible hacerla.
La estructura try/except
try:
# código que puede lanzar una excepción
except TipoDeExcepcion:
# código que se ejecuta si ocurre esa excepción
Cuando ocurre una excepción dentro del try, Python salta inmediatamente al except correspondiente, las instrucciones que quedan en el try no se ejecutan:
try:
a = int(input('a: '))
b = int(input('b: '))
print(a // b) # (1)
print('Listo') # (2)
except ZeroDivisionError:
print('División por cero') # (3)
Si b es 0, ocurre la excepción en (1), (2) no se ejecuta y se pasa directamente a (3).
Except específico — capturar excepciones concretas
Lo más recomendable es capturar excepciones específicas, así sabes exactamente qué error ocurrió y puedes gestionarlo de forma apropiada:
try:
a = int(input('a: '))
b = int(input('b: '))
print(a // b)
except ValueError:
print('Introduce números enteros')
except ZeroDivisionError:
print('No se puede dividir entre cero')
Si el usuario introduce texto, ocurre ValueError, lo captura el primer except. Si introduce cero, ocurre ZeroDivisionError, lo captura el segundo. Cada excepción tiene su bloque de gestión.
Las excepciones más comunes en FP2:
ValueError # valor incorrecto para la operación (int('hola'))
ZeroDivisionError # división entre cero
TypeError # tipo de dato incorrecto
IndexError # índice fuera de rango en lista o cadena
KeyError # clave no encontrada en diccionario
FileNotFoundError # fichero no encontrado
AttributeError # atributo o método no existe en el objeto
Capturar varias excepciones en un mismo bloque
Si quieres gestionar varias excepciones de la misma manera, puedes agruparlas en una tupla:
try:
a = int(input('a: '))
b = int(input('b: '))
print(a // b)
except (ValueError, ZeroDivisionError):
print('Error en los datos de entrada')
Except genérico — la diferencia que más confunde
El except sin tipo captura cualquier excepción, es el equivalente al else final de un if/elif:
try:
a = int(input('a: '))
b = int(input('b: '))
print(a // b)
except ValueError:
print('Introduce números enteros')
except ZeroDivisionError:
print('No se puede dividir entre cero')
except:
print('Ocurrió un error inesperado')
El except genérico va siempre al final y captura cualquier excepción no contemplada antes. Úsalo con moderación, si capturas todo sin saber qué es, puedes ocultar errores reales en tu código.
Acceder a la información de la excepción
Puedes dar nombre al objeto excepción para acceder a su información:
try:
a = int(input('a: '))
b = int(input('b: '))
print(a // b)
except Exception as err:
print('Error:', err) # mensaje del error
print(type(err)) # tipo de excepción
print(err.args) # información adicional como tupla
a: 7
b: 0
Error: integer division or modulo by zero
<class 'ZeroDivisionError'>
('integer division or modulo by zero',)
Exception es la clase base de la que heredan todos los tipos de excepciones. Úsala cuando quieras capturar cualquier excepción pero necesites acceder a su información.
Los bloques adicionales — else y finally
Además de except, un bloque try puede tener else y finally:
try:
a = int(input('a: '))
b = int(input('b: '))
resultado = a // b
except ValueError:
print('Introduce números enteros')
except ZeroDivisionError:
print('No se puede dividir entre cero')
else:
print(resultado) # solo se ejecuta si NO hubo excepción
finally:
print('Ejecución terminada') # se ejecuta SIEMPRE
else — se ejecuta solo si el bloque try no lanzó ninguna excepción. Es el lugar para código que depende de que el try haya tenido éxito pero que no necesita estar protegido.
finally — se ejecuta siempre, haya o no excepción. Su uso principal es liberar recursos, cerrar ficheros, conexiones de red, bases de datos, que deben cerrarse pase lo que pase.
# Sin excepción: a: 10 / b: 2 → 5 Ejecución terminada # Con excepción: a: 10 / b: 0 → No se puede dividir entre cero Ejecución terminada
El finally es el bloque que más confunde al principio, recuerda: siempre se ejecuta, incluso si hay una excepción no capturada o un return dentro del try.
La sentencia raise — lanzar excepciones propias
Hasta ahora Python lanzaba las excepciones. Con raise puedes lanzarlas tú cuando detectas una situación anómala:
import math
def area_circulo(radio):
if radio < 0:
raise ValueError('El radio no puede ser negativo')
return math.pi * radio ** 2
try:
print(area_circulo(5)) # → 78.539...
print(area_circulo(-2)) # lanza ValueError
except ValueError as err:
print(err) # → El radio no puede ser negativo
raise acepta cualquier tipo de excepción con un mensaje descriptivo:
raise ValueError('mensaje descriptivo')
raise TypeError('tipo de dato incorrecto')
raise ZeroDivisionError('el denominador es cero')
raise ValueError # sin mensaje — usa el mensaje por defecto
raise sin argumentos — propagar excepciones
Dentro de un except puedes usar raise solo para propagar la excepción hacia el código que llamó a tu función:
def leer_numero():
intentos = 0
while True:
try:
return int(input('Número: '))
except ValueError:
intentos += 1
print('Introduce un entero')
if intentos >= 3:
raise # propaga el ValueError hacia fuera
try:
leer_numero()
except ValueError:
print('Demasiados errores — programa terminado')
Número: a Introduce un entero Número: b Introduce un entero Número: c Introduce un entero Demasiados errores — programa terminado
El raise sin argumentos dentro del except relanza la misma excepción que se está gestionando. Si nadie la captura, el programa termina.
Crear excepciones propias
Puedes definir tus propios tipos de excepción creando una clase que herede de Exception:
class ErrorRadioNegativo(Exception):
pass
class ErrorRadioTipo(Exception):
pass
import math
def area_circulo(radio):
if not (type(radio) == int or type(radio) == float):
raise ErrorRadioTipo('El radio debe ser un número')
if radio < 0:
raise ErrorRadioNegativo('El radio no puede ser negativo')
return math.pi * radio ** 2
try:
print(area_circulo('cinco'))
except ErrorRadioTipo as err:
print(err)
except ErrorRadioNegativo as err:
print(err)
El radio debe ser un número
Por convención los nombres de excepciones propias terminan en Error, igual que las predefinidas (ValueError, TypeError…). Una excepción propia puede ser tan simple como pass o incluir atributos adicionales con información sobre el error.
Asertos — assert
Un aserto es una condición que debe ser verdadera en un punto concreto del programa para que pueda continuar:
def media(elementos):
assert len(elementos) != 0, 'La lista no puede estar vacía'
return sum(elementos) / len(elementos)
print(media([1, 2, 3])) # → 2.0
print(media([])) # → AssertionError: La lista no puede estar vacía
assert condicion, mensaje, si la condición es falsa lanza AssertionError con el mensaje. El mensaje es opcional.
Los asertos son equivalentes a:
if __debug__:
if len(elementos) == 0:
raise AssertionError('La lista no puede estar vacía')
Cuándo usar assert vs raise:
# USA assert para — condiciones que nunca deberían ocurrir
# si el programa está correctamente escrito (errores de programación)
assert len(lista) > 0
# USA raise para — situaciones anómalas esperables en producción
# que el usuario o el entorno pueden provocar
if radio < 0:
raise ValueError('Radio negativo')
Los asertos pueden desactivarse ejecutando Python con la opción -O (optimización). Por eso nunca los uses para validar entrada del usuario, usa raise para eso.
Visualízalo con Python Tutor
Copia este código en pythontutor.com:
def dividir(a, b):
if b == 0:
raise ZeroDivisionError('No se puede dividir entre cero')
return a / b
try:
resultado = dividir(10, 0)
except ZeroDivisionError as err:
print(err)
finally:
print('Siempre se ejecuta')
Ejecuta paso a paso y observa cómo el raise interrumpe la función y el flujo salta directamente al except, y cómo el finally se ejecuta en cualquier caso.
Resumen rápido
# ESTRUCTURA BÁSICA
try:
# código que puede fallar
except TipoEspecifico:
# gestión específica
except (Tipo1, Tipo2):
# varios tipos juntos
except Exception as err:
# cualquier excepción + acceso a info
except:
# cualquier excepción (sin info)
else:
# solo si NO hubo excepción
finally:
# SIEMPRE se ejecuta
# RAISE
raise ValueError('mensaje') # lanzar excepción
raise # propagar excepción actual
# EXCEPCIÓN PROPIA
class MiError(Exception):
pass
raise MiError('mensaje')
# ASSERT
assert condicion, 'mensaje' # AssertionError si falsa
# try/except vs if/else
# if/else → prevenir errores conocidos y comprobables
# try/except → gestionar errores inesperados o en recursos externos
# EXCEPCIONES COMUNES
# ValueError — valor incorrecto
# ZeroDivisionError — división entre cero
# TypeError — tipo incorrecto
# IndexError — índice fuera de rango
# KeyError — clave no encontrada
# FileNotFoundError — fichero no existe
En el próximo artículo practicamos las excepciones con programas reales, validadores robustos, gestión de ficheros y excepciones propias en acción.
Exceptions in Python — try, except, raise and assert without complications
Python exceptions are the mechanism that lets a program handle unexpected situations without crashing. In FP1 you learned to prevent errors with if/else. In FP2 you go further, catching them when they happen and raising them yourself when needed.
What is an exception?
When Python encounters a situation it can’t handle it raises an exception and the program stops:
a = int(input('a: '))
b = int(input('b: '))
print(a // b)
a: 7 b: 0 ZeroDivisionError: integer division or modulo by zero
try/except vs if/else — when to use each
# if/else — prevention
if b != 0:
print(a // b)
else:
print('Cannot divide by zero')
# try/except — management
try:
print(a // b)
except ZeroDivisionError:
print('Cannot divide by zero')
Use if/else when you can check the condition cheaply before the error occurs. Use try/except when you can’t know in advance if an error will occur, user input, file access, external resources.
try/except structure
try:
a = int(input('a: '))
b = int(input('b: '))
print(a // b) # (1)
print('Done') # (2) — skipped if exception at (1)
except ZeroDivisionError:
print('Division by zero')
Specific except
try:
a = int(input('a: '))
b = int(input('b: '))
print(a // b)
except ValueError:
print('Enter integer numbers')
except ZeroDivisionError:
print('Cannot divide by zero')
Multiple exceptions in one block:
except (ValueError, ZeroDivisionError):
print('Input error')
Generic except
except ValueError:
print('Enter integers')
except ZeroDivisionError:
print('Cannot divide by zero')
except:
print('Unexpected error') # catches everything else — use sparingly
Accessing exception information
except Exception as err:
print('Error:', err)
print(type(err))
print(err.args)
else and finally
try:
result = a // b
except ZeroDivisionError:
print('Cannot divide by zero')
else:
print(result) # only if NO exception occurred
finally:
print('Always runs') # runs regardless
raise — throwing your own exceptions
import math
def circle_area(radius):
if radius < 0:
raise ValueError('Radius cannot be negative')
return math.pi * radius ** 2
try:
print(circle_area(-2))
except ValueError as err:
print(err)
raise without arguments — propagating exceptions
def read_number():
attempts = 0
while True:
try:
return int(input('Number: '))
except ValueError:
attempts += 1
if attempts >= 3:
raise # propagates ValueError upward
try:
read_number()
except ValueError:
print('Too many errors')
Custom exceptions
class NegativeRadiusError(Exception):
pass
def circle_area(radius):
if radius < 0:
raise NegativeRadiusError('Radius cannot be negative')
return math.pi * radius ** 2
assert
def average(items):
assert len(items) != 0, 'List cannot be empty'
return sum(items) / len(items)
assert vs raise:
assert condition # programming errors — can be disabled with -O raise ValueError() # expected runtime errors — always active
Quick summary
# STRUCTURE
try:
pass
except SpecificType:
pass
except (Type1, Type2):
pass
except Exception as err:
pass
except:
pass
else:
pass # only if no exception
finally:
pass # always
# RAISE
raise ValueError('message')
raise # propagate current exception
# CUSTOM EXCEPTION
class MyError(Exception):
pass
# ASSERT
assert condition, 'message'
# COMMON EXCEPTIONS
# ValueError, ZeroDivisionError, TypeError
# IndexError, KeyError, FileNotFoundError

Un comentario