excepciones en Python práctica try except raise programas

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.

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.

Publicaciones Similares

2 comentarios

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *