Excepciones en Python — 4 programas reales que no se rompen
Las excepciones en Python práctica real es lo que toca ahora. En el artículo anterior vimos la teoría. Ahora escribimos cuatro programas que no se rompen ante ninguna entrada inesperada. Cada uno demuestra un uso real de las excepciones que verás en FP2.
Tabla de Contenidos
Excepciones en Python práctica — Programa 1: Validador robusto de datos
El validador más completo que puedes escribir en FP2, pide cuatro tipos de datos diferentes y gestiona todos los posibles errores con excepciones específicas:
def pedir_entero(mensaje, minimo=None, maximo=None):
while True:
try:
valor = int(input(mensaje))
if minimo is not None and valor < minimo:
raise ValueError(f'El valor debe ser >= {minimo}')
if maximo is not None and valor > maximo:
raise ValueError(f'El valor debe ser <= {maximo}')
return valor
except ValueError as err:
print(f'Error: {err}')
def pedir_flotante(mensaje, minimo=None, maximo=None):
while True:
try:
valor = float(input(mensaje))
if minimo is not None and valor < minimo:
raise ValueError(f'El valor debe ser >= {minimo}')
if maximo is not None and valor > maximo:
raise ValueError(f'El valor debe ser <= {maximo}')
return valor
except ValueError as err:
print(f'Error: {err}')
def pedir_opcion(mensaje, opciones_validas):
while True:
try:
opcion = input(mensaje).strip().lower()
if opcion not in opciones_validas:
raise ValueError(f'Opciones válidas: {opciones_validas}')
return opcion
except ValueError as err:
print(f'Error: {err}')
# Programa principal
print('=== FICHA DE ESTUDIANTE ===\n')
nombre = input('Nombre: ').strip()
if not nombre:
nombre = 'Sin nombre'
edad = pedir_entero('Edad (16-99): ', minimo=16, maximo=99)
nota = pedir_flotante('Nota media (0-10): ', minimo=0.0, maximo=10.0)
modalidad = pedir_opcion('Modalidad (presencial/online): ',
['presencial', 'online'])
print(f'\n--- Ficha guardada ---')
print(f'Nombre: {nombre}')
print(f'Edad: {edad} años')
print(f'Nota: {nota:.2f}')
print(f'Modalidad: {modalidad}')
=== FICHA DE ESTUDIANTE === Nombre: Sergio Edad (16-99): doce Error: invalid literal for int() with base 10: 'doce' Edad (16-99): 12 Error: El valor debe ser >= 16 Edad (16-99): 22 Nota media (0-10): 11 Error: El valor debe ser <= 10 Nota media (0-10): 7.5 Modalidad (presencial/online): virtual Error: Opciones válidas: ['presencial', 'online'] Modalidad (presencial/online): presencial --- Ficha guardada --- Nombre: Sergio Edad: 22 años Nota: 7.50 Modalidad: presencial
Fíjate en cómo pedir_entero captura el ValueError que lanza int() cuando la entrada no es un número, y también lanza su propio ValueError cuando el número está fuera de rango. Un solo except ValueError gestiona los dos casos.
Excepciones en Python práctica — Programa 2: Calculadora que no se rompe
Una calculadora completa que gestiona todas las entradas posibles sin romperse nunca:
def obtener_numero(mensaje):
while True:
try:
return float(input(mensaje))
except ValueError:
print('Error: introduce un número válido')
def calcular(a, operacion, b):
try:
if operacion == '+':
return a + b
elif operacion == '-':
return a - b
elif operacion == '*':
return a * b
elif operacion == '/':
if b == 0:
raise ZeroDivisionError('No se puede dividir entre cero')
return a / b
elif operacion == '//':
if b == 0:
raise ZeroDivisionError('No se puede dividir entre cero')
return a // b
elif operacion == '%':
if b == 0:
raise ZeroDivisionError('No se puede usar módulo con cero')
return a % b
elif operacion == '**':
return a ** b
else:
raise ValueError(f'Operación desconocida: {operacion}')
except ZeroDivisionError as err:
print(f'Error matemático: {err}')
return None
except ValueError as err:
print(f'Error de operación: {err}')
return None
def mostrar_menu():
print('\n=== CALCULADORA ===')
print('Operaciones: + - * / // % **')
print('Escribe "salir" para terminar')
operaciones_validas = {'+', '-', '*', '/', '//', '%', '**'}
mostrar_menu()
while True:
entrada = input('\nOperación (ej: 10 + 3): ').strip()
if entrada.lower() == 'salir':
print('Hasta luego')
break
try:
partes = entrada.split()
if len(partes) != 3:
raise ValueError('Formato incorrecto — usa: número operación número')
a = float(partes[0])
operacion = partes[1]
b = float(partes[2])
if operacion not in operaciones_validas:
raise ValueError(f'Operación no válida: {operacion}')
resultado = calcular(a, operacion, b)
if resultado is not None:
print(f'Resultado: {a} {operacion} {b} = {resultado}')
except ValueError as err:
print(f'Error: {err}')
=== CALCULADORA === Operaciones: + - * / // % ** Escribe "salir" para terminar Operación (ej: 10 + 3): 10 / 0 Error matemático: No se puede dividir entre cero Operación (ej: 10 + 3): diez + 3 Error: could not convert string to float: 'diez' Operación (ej: 10 + 3): 10 ^ 3 Error: Operación no válida: ^ Operación (ej: 10 + 3): 10 ** 3 Resultado: 10.0 ** 3.0 = 1000.0
Excepciones en Python práctica — Programa 3: Lector de ficheros con gestión de errores
Gestionar ficheros sin excepciones es peligroso, el fichero puede no existir, puede estar corrupto o puede haber problemas de permisos. Con try/except/finally el fichero siempre se cierra correctamente:
def leer_notas_fichero(nombre_fichero):
fichero = None
try:
fichero = open(nombre_fichero, 'r', encoding='utf-8')
notas = []
for linea in fichero:
linea = linea.strip()
if linea:
try:
nota = float(linea)
if 0 <= nota <= 10:
notas.append(nota)
else:
print(f'Nota fuera de rango ignorada: {nota}')
except ValueError:
print(f'Línea no válida ignorada: {linea}')
return notas
except FileNotFoundError:
print(f'Error: el fichero "{nombre_fichero}" no existe')
return []
except PermissionError:
print(f'Error: sin permisos para leer "{nombre_fichero}"')
return []
except Exception as err:
print(f'Error inesperado: {err}')
return []
finally:
if fichero is not None:
fichero.close()
print('Fichero cerrado correctamente')
def analizar_notas(notas):
if not notas:
print('No hay notas para analizar')
return
print(f'\n--- Análisis de {len(notas)} notas ---')
print(f'Media: {sum(notas)/len(notas):.2f}')
print(f'Máxima: {max(notas):.2f}')
print(f'Mínima: {min(notas):.2f}')
aprobados = sum(1 for n in notas if n >= 5)
print(f'Aprobados: {aprobados}/{len(notas)}')
# Uso del programa
nombre = input('Nombre del fichero de notas: ')
notas = leer_notas_fichero(nombre)
analizar_notas(notas)
Nombre del fichero de notas: notas.txt Línea no válida ignorada: ocho Nota fuera de rango ignorada: 11.0 Fichero cerrado correctamente --- Análisis de 5 notas --- Media: 7.20 Máxima: 9.50 Mínima: 4.00 Aprobados: 4/5
El finally garantiza que el fichero se cierra aunque ocurra cualquier excepción, incluso una no prevista. Es el patrón más importante cuando trabajas con recursos externos.
Excepciones en Python práctica — Programa 4: Clase con excepciones propias: números racionales
Este es el programa más completo, define una clase con sus propias excepciones para representar números racionales (fracciones):
# Excepciones propias
class ErrorRacional(Exception):
"""Clase base para errores de números racionales"""
pass
class ErrorDenominadorCero(ErrorRacional):
"""Se lanza cuando el denominador es cero"""
pass
class ErrorTipoIncorrecto(ErrorRacional):
"""Se lanza cuando el tipo de dato no es válido"""
pass
# Clase Racional
class Racional:
def __init__(self, numerador, denominador):
if not (isinstance(numerador, int) and isinstance(denominador, int)):
raise ErrorTipoIncorrecto('Numerador y denominador deben ser enteros')
if denominador == 0:
raise ErrorDenominadorCero('El denominador no puede ser cero')
self.numerador = numerador
self.denominador = denominador
def __str__(self):
return f'{self.numerador}/{self.denominador}'
def __add__(self, otro):
if not isinstance(otro, Racional):
raise ErrorTipoIncorrecto('Solo se pueden sumar racionales')
nuevo_num = self.numerador * otro.denominador + otro.numerador * self.denominador
nuevo_den = self.denominador * otro.denominador
return Racional(nuevo_num, nuevo_den)
def __eq__(self, otro):
if not isinstance(otro, Racional):
return False
return self.numerador * otro.denominador == otro.numerador * self.denominador
# Programa principal
print('=== CALCULADORA DE RACIONALES ===\n')
while True:
try:
num1 = int(input('Numerador 1: '))
den1 = int(input('Denominador 1: '))
r1 = Racional(num1, den1)
num2 = int(input('Numerador 2: '))
den2 = int(input('Denominador 2: '))
r2 = Racional(num2, den2)
suma = r1 + r2
print(f'\n{r1} + {r2} = {suma}')
print(f'¿Son iguales? {r1 == r2}')
break
except ErrorDenominadorCero as err:
print(f'Error de racional: {err}')
except ErrorTipoIncorrecto as err:
print(f'Error de tipo: {err}')
except ValueError:
print('Error: introduce números enteros')
=== CALCULADORA DE RACIONALES === Numerador 1: 3 Denominador 1: 0 Error de racional: El denominador no puede ser cero Numerador 1: 3 Denominador 1: 4 Numerador 2: 1 Denominador 2: 2 3/4 + 1/2 = 10/8 ¿Son iguales? False
Fíjate en la jerarquía de excepciones, ErrorDenominadorCero y ErrorTipoIncorrecto heredan de ErrorRacional. Esto permite capturarlas por separado o capturar ErrorRacional para gestionarlas todas a la vez. Es el patrón que más aparece en FP2 cuando diseñas clases propias.
Visualízalo con Python Tutor
Copia este código en pythontutor.com:
class ErrorDenominadorCero(Exception):
pass
def crear_racional(num, den):
if den == 0:
raise ErrorDenominadorCero('Denominador no puede ser cero')
return (num, den)
try:
r = crear_racional(3, 0)
except ErrorDenominadorCero as err:
print(f'Error capturado: {err}')
finally:
print('Siempre se ejecuta')
Observa cómo el raise interrumpe la función y el flujo salta al except, y cómo el finally se ejecuta en cualquier caso.
Resumen y siguiente paso
En este artículo practicaste las excepciones con cuatro programas reales. Los patrones clave que debes llevarte: except específico para gestionar errores concretos, finally para cerrar recursos siempre, raise para lanzar errores propios y jerarquías de excepciones para organizar los errores de tus clases.
En el siguiente artículo encontrarás ejercicios propuestos con solución para practicar por tu cuenta.
Python exceptions — 4 real programs that never break
Program 1 — Robust data validator
def get_integer(message, minimum=None, maximum=None):
while True:
try:
value = int(input(message))
if minimum is not None and value < minimum:
raise ValueError(f'Value must be >= {minimum}')
if maximum is not None and value > maximum:
raise ValueError(f'Value must be <= {maximum}')
return value
except ValueError as err:
print(f'Error: {err}')
def get_float(message, minimum=None, maximum=None):
while True:
try:
value = float(input(message))
if minimum is not None and value < minimum:
raise ValueError(f'Value must be >= {minimum}')
if maximum is not None and value > maximum:
raise ValueError(f'Value must be <= {maximum}')
return value
except ValueError as err:
print(f'Error: {err}')
def get_option(message, valid_options):
while True:
try:
option = input(message).strip().lower()
if option not in valid_options:
raise ValueError(f'Valid options: {valid_options}')
return option
except ValueError as err:
print(f'Error: {err}')
print('=== STUDENT PROFILE ===\n')
name = input('Name: ').strip() or 'No name'
age = get_integer('Age (16-99): ', minimum=16, maximum=99)
grade = get_float('Average grade (0-10): ', minimum=0.0, maximum=10.0)
mode = get_option('Mode (in-person/online): ', ['in-person', 'online'])
print(f'\n--- Profile saved ---')
print(f'Name: {name}')
print(f'Age: {age}')
print(f'Grade: {grade:.2f}')
print(f'Mode: {mode}')
get_integer catches the ValueError raised by int() for non-numeric input, and also raises its own ValueError when the number is out of range. One except ValueError handles both cases.
Program 2 — Calculator that never breaks
def get_number(message):
while True:
try:
return float(input(message))
except ValueError:
print('Error: enter a valid number')
def calculate(a, operation, b):
try:
if operation == '+': return a + b
elif operation == '-': return a - b
elif operation == '*': return a * b
elif operation == '/':
if b == 0: raise ZeroDivisionError('Cannot divide by zero')
return a / b
elif operation == '**': return a ** b
else: raise ValueError(f'Unknown operation: {operation}')
except ZeroDivisionError as err:
print(f'Math error: {err}')
return None
except ValueError as err:
print(f'Operation error: {err}')
return None
valid_ops = {'+', '-', '*', '/', '//', '%', '**'}
print('Calculator — format: number operation number')
print('Type "quit" to exit\n')
while True:
entry = input('Operation: ').strip()
if entry.lower() == 'quit':
break
try:
parts = entry.split()
if len(parts) != 3:
raise ValueError('Wrong format — use: number operation number')
a, op, b = float(parts[0]), parts[1], float(parts[2])
if op not in valid_ops:
raise ValueError(f'Invalid operation: {op}')
result = calculate(a, op, b)
if result is not None:
print(f'Result: {a} {op} {b} = {result}')
except ValueError as err:
print(f'Error: {err}')
Program 3 — File reader with error handling
def read_grades_file(filename):
file = None
try:
file = open(filename, 'r', encoding='utf-8')
grades = []
for line in file:
line = line.strip()
if line:
try:
grade = float(line)
if 0 <= grade <= 10:
grades.append(grade)
else:
print(f'Grade out of range ignored: {grade}')
except ValueError:
print(f'Invalid line ignored: {line}')
return grades
except FileNotFoundError:
print(f'Error: file "{filename}" not found')
return []
except PermissionError:
print(f'Error: no permission to read "{filename}"')
return []
except Exception as err:
print(f'Unexpected error: {err}')
return []
finally:
if file is not None:
file.close()
print('File closed correctly')
filename = input('Grades filename: ')
grades = read_grades_file(filename)
if grades:
print(f'\nAverage: {sum(grades)/len(grades):.2f}')
print(f'Max: {max(grades):.2f} | Min: {min(grades):.2f}')
finally guarantees the file closes even if an unexpected exception occurs. The most important pattern when working with external resources.
Program 4 — Class with custom exceptions: rational numbers
class RationalError(Exception):
pass
class ZeroDenominatorError(RationalError):
pass
class WrongTypeError(RationalError):
pass
class Rational:
def __init__(self, numerator, denominator):
if not (isinstance(numerator, int) and isinstance(denominator, int)):
raise WrongTypeError('Numerator and denominator must be integers')
if denominator == 0:
raise ZeroDenominatorError('Denominator cannot be zero')
self.numerator = numerator
self.denominator = denominator
def __str__(self):
return f'{self.numerator}/{self.denominator}'
def __add__(self, other):
if not isinstance(other, Rational):
raise WrongTypeError('Can only add rationals')
new_num = self.numerator * other.denominator + other.numerator * self.denominator
new_den = self.denominator * other.denominator
return Rational(new_num, new_den)
while True:
try:
n1 = int(input('Numerator 1: '))
d1 = int(input('Denominator 1: '))
r1 = Rational(n1, d1)
n2 = int(input('Numerator 2: '))
d2 = int(input('Denominator 2: '))
r2 = Rational(n2, d2)
print(f'\n{r1} + {r2} = {r1 + r2}')
break
except ZeroDenominatorError as err:
print(f'Rational error: {err}')
except WrongTypeError as err:
print(f'Type error: {err}')
except ValueError:
print('Error: enter integers')
Exception hierarchy, ZeroDenominatorError and WrongTypeError inherit from RationalError. Catch them separately or catch RationalError to handle all at once. The pattern that appears most in FP2 when designing your own classes.

Un comentario