Cristhian Villegas
Backend9 min read1 views

Curso Python #9: Manejo de Errores y Excepciones

Curso Python #9: Manejo de Errores y Excepciones

Manejo de Errores y Excepciones - Parte 9 de 10

Logo de Python

Bienvenido al articulo 9 de 10 del Curso de Python desde Cero. Hasta ahora hemos aprendido a crear programas cada vez mas complejos: funciones, listas, diccionarios, clases, archivos... Pero hay un problema que no hemos enfrentado directamente: ¿que pasa cuando algo sale mal?

Imagina que tu programa le pide al usuario un numero y el usuario escribe "hola". O que intentas abrir un archivo que no existe. O que tratas de dividir entre cero. En todos estos casos, Python lanzara un error y tu programa se detendra abruptamente.

En este articulo vas a aprender a manejar errores para que tu programa no se rompa, sino que reaccione de forma inteligente cuando algo inesperado suceda. Esta es una habilidad fundamental para cualquier programador profesional.

📌 Nota: Este es el penultimo articulo del curso. En el siguiente (Parte 10 de 10) aplicaremos todo lo aprendido en un proyecto final completo.

¿Que son los errores en Python?

Un error es simplemente algo que sale mal cuando Python intenta ejecutar tu codigo. Hay dos grandes categorias de errores:

Errores de sintaxis (SyntaxError)

Estos errores ocurren cuando escribes codigo que Python no puede entender. Es como escribir una frase sin sentido en espanol — el interprete no sabe que hacer con ella.

python
1# Error de sintaxis: falta cerrar el parentesis
2print("Hola"
3
4# Error de sintaxis: falta los dos puntos
5if True
6    print("Verdadero")
7
8# Error de sintaxis: mala indentacion
9def saludar():
10print("Hola")  # Falta la indentacion

Los errores de sintaxis se detectan antes de que el programa se ejecute. Python lee tu codigo, encuentra algo que no entiende y te muestra un mensaje de error indicando la linea del problema.

Excepciones (errores en tiempo de ejecucion)

Estos errores ocurren mientras el programa se esta ejecutando. La sintaxis es correcta, pero algo falla durante la ejecucion.

python
1# TypeError: intentar sumar un numero con un texto
2resultado = 5 + "hola"
3
4# ValueError: intentar convertir texto a numero
5numero = int("abc")
6
7# ZeroDivisionError: dividir entre cero
8resultado = 10 / 0
9
10# FileNotFoundError: abrir un archivo que no existe
11archivo = open("no_existe.txt")
12
13# IndexError: acceder a un indice que no existe
14lista = [1, 2, 3]
15print(lista[10])
16
17# KeyError: buscar una clave que no existe en un diccionario
18datos = {"nombre": "Ana"}
19print(datos["edad"])

Cada uno de estos errores tiene un nombre especifico (TypeError, ValueError, etc.) que te indica exactamente que tipo de problema ocurrio.

💡 Tip: Cuando Python te muestra un error, lee el mensaje completo. La ultima linea te dice el tipo de error y una descripcion. Las lineas anteriores te muestran donde ocurrio el error en tu codigo. Con practica, aprenderas a leer estos mensajes rapidamente.

El bloque try/except: atrapar errores

El bloque try/except es la herramienta principal para manejar errores en Python. La idea es simple: "intenta ejecutar este codigo, y si falla, haz esto otro en lugar de detenerte".

Sintaxis basica

python
1try:
2    # Codigo que podria fallar
3    numero = int(input("Escribe un numero: "))
4    print(f"Tu numero es: {numero}")
5except:
6    # Que hacer si algo falla
7    print("Eso no es un numero valido")

Veamos como funciona paso a paso:

  1. Python intenta ejecutar el codigo dentro de try
  2. Si todo va bien, el bloque except se ignora completamente
  3. Si ocurre un error, Python deja de ejecutar el try y salta al bloque except
  4. Despues del except, el programa continua normalmente

Ejemplo practico: calculadora segura

python
1# Sin manejo de errores - el programa se rompe
2dividendo = int(input("Dividendo: "))   # Si escribes "abc", CRASH
3divisor = int(input("Divisor: "))       # Si escribes 0, CRASH
4resultado = dividendo / divisor
5print(f"Resultado: {resultado}")
6
7# Con manejo de errores - el programa es robusto
8try:
9    dividendo = int(input("Dividendo: "))
10    divisor = int(input("Divisor: "))
11    resultado = dividendo / divisor
12    print(f"Resultado: {resultado}")
13except:
14    print("Ocurrio un error. Verifica los datos ingresados.")

Con el try/except, si el usuario escribe algo incorrecto, el programa muestra un mensaje amigable en lugar de romperse.

Atrapar excepciones especificas

Usar un except generico (sin especificar el tipo de error) funciona, pero no es recomendable. ¿Por que? Porque no sabes que error ocurrio, y no puedes dar un mensaje util al usuario.

Es mucho mejor atrapar excepciones especificas:

python
1try:
2    dividendo = int(input("Dividendo: "))
3    divisor = int(input("Divisor: "))
4    resultado = dividendo / divisor
5    print(f"Resultado: {resultado}")
6except ValueError:
7    print("Error: debes escribir numeros enteros, no letras.")
8except ZeroDivisionError:
9    print("Error: no puedes dividir entre cero.")
10except Exception as e:
11    print(f"Error inesperado: {e}")

Ahora el programa da un mensaje diferente segun el tipo de error. Esto es mucho mas util para el usuario.

Capturar el mensaje de error

Puedes guardar el error en una variable usando as para acceder a su mensaje:

python
1try:
2    numero = int("hola")
3except ValueError as error:
4    print(f"Ocurrio un error: {error}")
5    # Imprime: Ocurrio un error: invalid literal for int() with base 10: 'hola'

Excepciones mas comunes

Excepcion Cuando ocurre Ejemplo
ValueError Valor incorrecto para la operacion int("abc")
TypeError Tipo de dato incorrecto "hola" + 5
ZeroDivisionError Division entre cero 10 / 0
FileNotFoundError Archivo no encontrado open("noexiste.txt")
IndexError Indice fuera de rango [1,2][5]
KeyError Clave no existe en diccionario {"a":1}["b"]
AttributeError Atributo o metodo no existe "hola".append("x")
ImportError No se puede importar un modulo import noexiste

Los bloques else y finally

Ademas de try y except, Python tiene dos bloques adicionales que hacen el manejo de errores mas completo: else y finally.

El bloque else

El bloque else se ejecuta solo si NO hubo ningun error en el try. Es util para separar el codigo que podria fallar del codigo que depende de que todo salga bien.

python
1try:
2    numero = int(input("Escribe un numero: "))
3except ValueError:
4    print("Eso no es un numero valido.")
5else:
6    # Solo se ejecuta si no hubo error
7    print(f"El doble de tu numero es: {numero * 2}")
8    print(f"La mitad de tu numero es: {numero / 2}")

El bloque finally

El bloque finally se ejecuta siempre, haya o no error. Es perfecto para tareas de limpieza, como cerrar archivos o conexiones.

python
1try:
2    archivo = open("datos.txt", "r")
3    contenido = archivo.read()
4    print(contenido)
5except FileNotFoundError:
6    print("El archivo no existe.")
7finally:
8    # Esto se ejecuta SIEMPRE
9    print("Operacion de lectura finalizada.")
10
11# Ejemplo con archivo que se cierra siempre
12archivo = None
13try:
14    archivo = open("datos.txt", "r")
15    contenido = archivo.read()
16except FileNotFoundError:
17    print("Archivo no encontrado.")
18finally:
19    if archivo:
20        archivo.close()
21        print("Archivo cerrado correctamente.")

Estructura completa: try/except/else/finally

python
1try:
2    # Codigo que podria fallar
3    numero = int(input("Ingresa un numero: "))
4    resultado = 100 / numero
5except ValueError:
6    print("Error: ingresa un numero valido.")
7except ZeroDivisionError:
8    print("Error: no puedes dividir entre cero.")
9else:
10    # Solo si no hubo error
11    print(f"100 / {numero} = {resultado}")
12finally:
13    # Siempre se ejecuta
14    print("Gracias por usar la calculadora.")
📌 Nota: El orden siempre es: tryexceptelsefinally. No puedes cambiar el orden. El else y finally son opcionales.

Lanzar excepciones con raise

Hasta ahora hemos atrapado errores que Python genera. Pero tambien puedes generar tus propios errores a proposito usando raise. Esto es util cuando quieres validar datos y detener la ejecucion si algo no es correcto.

python
1def establecer_edad(edad):
2    if edad < 0:
3        raise ValueError("La edad no puede ser negativa")
4    if edad > 150:
5        raise ValueError("La edad no puede ser mayor a 150")
6    return edad
7
8# Uso
9try:
10    mi_edad = establecer_edad(-5)
11except ValueError as e:
12    print(f"Error: {e}")
13    # Imprime: Error: La edad no puede ser negativa

¿Cuando usar raise?

Usa raise cuando tu funcion recibe datos que no tienen sentido para tu logica de negocio:

python
1def transferir_dinero(origen, destino, cantidad):
2    if cantidad <= 0:
3        raise ValueError("La cantidad debe ser positiva")
4    if cantidad > origen.saldo:
5        raise ValueError("Saldo insuficiente")
6
7    origen.saldo -= cantidad
8    destino.saldo += cantidad
9    return True
10
11def registrar_usuario(nombre, email):
12    if not nombre or not nombre.strip():
13        raise ValueError("El nombre no puede estar vacio")
14    if "@" not in email:
15        raise ValueError("El email no es valido")
16
17    # Continuar con el registro...
18    print(f"Usuario {nombre} registrado con {email}")
💡 Tip: Es una buena practica validar los datos al inicio de tus funciones y lanzar excepciones claras si algo esta mal. Esto se llama "fallar rapido" (fail fast) y hace que los errores sean mas faciles de encontrar y corregir.

Crear excepciones personalizadas

Puedes crear tus propios tipos de excepciones creando una clase que herede de Exception. Esto es util en proyectos grandes para tener errores especificos de tu aplicacion.

python
1# Definir excepciones personalizadas
2class SaldoInsuficienteError(Exception):
3    """Se lanza cuando no hay suficiente saldo para la operacion."""
4    def __init__(self, saldo_actual, cantidad_solicitada):
5        self.saldo_actual = saldo_actual
6        self.cantidad_solicitada = cantidad_solicitada
7        mensaje = f"Saldo insuficiente. Tienes {saldo_actual}, pero necesitas {cantidad_solicitada}"
8        super().__init__(mensaje)
9
10class EdadInvalidaError(Exception):
11    """Se lanza cuando la edad no esta en un rango valido."""
12    pass
13
14class EmailInvalidoError(Exception):
15    """Se lanza cuando el formato del email es incorrecto."""
16    pass

Usando excepciones personalizadas

python
1class CuentaBancaria:
2    def __init__(self, titular, saldo=0):
3        self.titular = titular
4        self.saldo = saldo
5
6    def retirar(self, cantidad):
7        if cantidad <= 0:
8            raise ValueError("La cantidad debe ser positiva")
9        if cantidad > self.saldo:
10            raise SaldoInsuficienteError(self.saldo, cantidad)
11
12        self.saldo -= cantidad
13        return self.saldo
14
15# Uso
16cuenta = CuentaBancaria("Carlos", 1000)
17
18try:
19    cuenta.retirar(1500)
20except SaldoInsuficienteError as e:
21    print(f"No se pudo retirar: {e}")
22    print(f"Tu saldo actual: {e.saldo_actual}")
23    # Imprime: No se pudo retirar: Saldo insuficiente. Tienes $1000, pero necesitas $1500
24    # Tu saldo actual: $1000
📌 Nota: El nombre de tus excepciones personalizadas siempre debe terminar en Error (por convencion). Ejemplos: SaldoInsuficienteError, UsuarioNoEncontradoError, ConexionFallidaError.

Buenas practicas en el manejo de errores

Ahora que sabes como usar try/except, raise y excepciones personalizadas, veamos las reglas que siguen los programadores profesionales:

1. Nunca uses except generico sin tipo

python
1# MAL - atrapa TODO, incluso errores graves
2try:
3    resultado = hacer_algo()
4except:
5    pass  # Silencia todos los errores
6
7# BIEN - atrapa solo lo que esperas
8try:
9    resultado = hacer_algo()
10except ValueError as e:
11    print(f"Valor invalido: {e}")
12except FileNotFoundError as e:
13    print(f"Archivo no encontrado: {e}")

2. Nunca uses pass en un except (silenciar errores)

python
1# MAL - el error desaparece y nunca sabras que fallo
2try:
3    dato = int(input("Numero: "))
4except ValueError:
5    pass
6
7# BIEN - al menos registra el error
8try:
9    dato = int(input("Numero: "))
10except ValueError:
11    print("Por favor ingresa un numero valido.")
12    dato = 0  # Valor por defecto

3. Pon solo lo necesario dentro del try

python
1# MAL - demasiado codigo en el try
2try:
3    nombre = input("Nombre: ")
4    edad = int(input("Edad: "))
5    email = input("Email: ")
6    ciudad = input("Ciudad: ")
7    print(f"Datos: {nombre}, {edad}, {email}, {ciudad}")
8except ValueError:
9    print("Error en la edad")
10
11# BIEN - solo la linea que puede fallar
12nombre = input("Nombre: ")
13try:
14    edad = int(input("Edad: "))
15except ValueError:
16    print("Error: la edad debe ser un numero")
17    edad = 0
18email = input("Email: ")
19ciudad = input("Ciudad: ")

4. Usa logging en lugar de print para errores en produccion

python
1import logging
2
3logging.basicConfig(level=logging.INFO)
4logger = logging.getLogger(__name__)
5
6try:
7    resultado = procesar_datos(datos)
8except ValueError as e:
9    logger.error(f"Error al procesar datos: {e}")
10except Exception as e:
11    logger.critical(f"Error inesperado: {e}", exc_info=True)

5. Usa with para manejo automatico de recursos

python
1# En lugar de try/finally para cerrar archivos, usa with
2with open("datos.txt", "r") as archivo:
3    contenido = archivo.read()
4# El archivo se cierra automaticamente, incluso si hay un error

Ejemplo practico: validador de datos del usuario

Vamos a crear un programa completo que pide datos al usuario y maneja todos los posibles errores de forma elegante:

python
1class ValidacionError(Exception):
2    """Error personalizado para validaciones."""
3    pass
4
5def pedir_nombre():
6    """Pide y valida el nombre del usuario."""
7    nombre = input("Ingresa tu nombre: ").strip()
8    if not nombre:
9        raise ValidacionError("El nombre no puede estar vacio")
10    if len(nombre) < 2:
11        raise ValidacionError("El nombre debe tener al menos 2 caracteres")
12    if any(char.isdigit() for char in nombre):
13        raise ValidacionError("El nombre no puede contener numeros")
14    return nombre
15
16def pedir_edad():
17    """Pide y valida la edad del usuario."""
18    entrada = input("Ingresa tu edad: ").strip()
19    try:
20        edad = int(entrada)
21    except ValueError:
22        raise ValidacionError(f"'{entrada}' no es un numero valido")
23
24    if edad < 0:
25        raise ValidacionError("La edad no puede ser negativa")
26    if edad > 120:
27        raise ValidacionError("La edad no parece valida (mayor a 120)")
28    return edad
29
30def pedir_email():
31    """Pide y valida el email del usuario."""
32    email = input("Ingresa tu email: ").strip()
33    if not email:
34        raise ValidacionError("El email no puede estar vacio")
35    if "@" not in email:
36        raise ValidacionError("El email debe contener @")
37    if "." not in email.split("@")[1]:
38        raise ValidacionError("El dominio del email debe contener un punto")
39    return email
40
41def pedir_numero_telefono():
42    """Pide y valida un numero de telefono."""
43    telefono = input("Ingresa tu telefono (10 digitos): ").strip()
44    if not telefono.isdigit():
45        raise ValidacionError("El telefono solo debe contener numeros")
46    if len(telefono) != 10:
47        raise ValidacionError(f"El telefono debe tener 10 digitos, tiene {len(telefono)}")
48    return telefono
49
50def pedir_dato_con_reintentos(funcion_pedir, max_intentos=3):
51    """Ejecuta una funcion de peticion con reintentos."""
52    for intento in range(1, max_intentos + 1):
53        try:
54            return funcion_pedir()
55        except ValidacionError as e:
56            print(f"  Error: {e}")
57            if intento < max_intentos:
58                print(f"  Intento {intento} de {max_intentos}. Intenta de nuevo.")
59            else:
60                print(f"  Agotaste los {max_intentos} intentos.")
61                return None
62
63def main():
64    print("=" * 50)
65    print("   FORMULARIO DE REGISTRO")
66    print("=" * 50)
67    print()
68
69    nombre = pedir_dato_con_reintentos(pedir_nombre)
70    if not nombre:
71        print("No se pudo obtener el nombre. Saliendo...")
72        return
73
74    edad = pedir_dato_con_reintentos(pedir_edad)
75    if edad is None:
76        print("No se pudo obtener la edad. Saliendo...")
77        return
78
79    email = pedir_dato_con_reintentos(pedir_email)
80    if not email:
81        print("No se pudo obtener el email. Saliendo...")
82        return
83
84    telefono = pedir_dato_con_reintentos(pedir_numero_telefono)
85    if not telefono:
86        print("No se pudo obtener el telefono. Saliendo...")
87        return
88
89    # Todos los datos son validos
90    print()
91    print("=" * 50)
92    print("   REGISTRO EXITOSO")
93    print("=" * 50)
94    print(f"  Nombre:   {nombre}")
95    print(f"  Edad:     {edad} anios")
96    print(f"  Email:    {email}")
97    print(f"  Telefono: {telefono}")
98
99if __name__ == "__main__":
100    main()
💡 Tip: Observa como el programa nunca se rompe sin importar lo que escriba el usuario. Cada entrada se valida, se le da retroalimentacion clara al usuario, y se le permite reintentar. Este es el nivel de robustez que debes buscar en tus programas.

Resumen y proximo articulo

En este articulo aprendimos todo sobre el manejo de errores en Python:

  • La diferencia entre errores de sintaxis y excepciones
  • Como usar try/except para atrapar errores sin que el programa se detenga
  • La importancia de atrapar excepciones especificas (ValueError, TypeError, etc.)
  • Los bloques else (se ejecuta si no hay error) y finally (se ejecuta siempre)
  • Como lanzar excepciones con raise para validar datos
  • Como crear excepciones personalizadas para tu aplicacion
  • Las buenas practicas: no usar except generico, no silenciar errores, usar logging
  • Un ejemplo completo de un programa robusto que valida datos del usuario

En el proximo y ultimo articulo (Parte 10 de 10) vamos a construir un proyecto final completo: un gestor de tareas en la terminal. Aplicaremos absolutamente todo lo que hemos aprendido en el curso: variables, funciones, listas, diccionarios, POO, archivos y manejo de errores.

¡Preparate para poner en practica todo lo aprendido!

Share:
CV

Cristhian Villegas

Software Engineer specializing in Java, Spring Boot, Angular & AWS. Building scalable distributed systems with clean architecture.

Comments

Sign in to leave a comment

No comments yet. Be the first!

Related Articles