Python Try Except: Cómo manejar excepciones correctamente
Updated on
Su script de Python lee un archivo, analiza los datos y los envía a una API. Funciona perfectamente en su máquina. Luego se ejecuta en un servidor donde la ruta del archivo es incorrecta, el JSON está malformado o la red está caída. El programa se bloquea con un traceback y toda la pipeline se detiene. Este es el problema que resuelve try/except. En lugar de dejar que los errores maten su programa, los captura, los maneja y sigue ejecutándose.
¿Qué son las excepciones en Python?
Una excepción es un evento que interrumpe el flujo normal de un programa. Cuando Python encuentra una operación que no puede realizar -- dividir por cero, acceder a una clave de diccionario inexistente, abrir un archivo que no existe -- crea un objeto de excepción y detiene la ejecución. Si nada captura esa excepción, el programa termina e imprime un traceback.
# This crashes the program
result = 10 / 0Salida:
Traceback (most recent call last):
File "example.py", line 2, in <module>
result = 10 / 0
ZeroDivisionError: division by zeroLas excepciones son diferentes de los errores de sintaxis. Un error de sintaxis significa que Python no puede analizar su código en absoluto. Una excepción ocurre durante la ejecución, después de que el código ha sido analizado exitosamente.
Sintaxis básica de Try/Except
El bloque try/except le permite intentar código que podría fallar y definir qué sucede si falla.
try:
result = 10 / 0
except ZeroDivisionError:
print("Cannot divide by zero.")Salida:
Cannot divide by zero.El programa no se bloquea. Python ejecuta el código dentro de try. Cuando ocurre ZeroDivisionError, la ejecución salta al bloque except. Todo después del try/except continúa normalmente.
También puede capturar el objeto de excepción para inspeccionar el mensaje de error:
try:
result = 10 / 0
except ZeroDivisionError as e:
print(f"Error: {e}")Salida:
Error: division by zeroCapturando excepciones específicas
Python tiene docenas de tipos de excepciones integradas. Capturar la correcta hace que su código sea preciso. Aquí están las excepciones más comunes que manejará.
ValueError
Se lanza cuando una función recibe un argumento con el tipo correcto pero un valor inapropiado.
try:
number = int("not_a_number")
except ValueError as e:
print(f"Invalid value: {e}")Salida:
Invalid value: invalid literal for int() with base 10: 'not_a_number'TypeError
Se lanza cuando una operación se aplica a un objeto del tipo incorrecto.
try:
result = "hello" + 42
except TypeError as e:
print(f"Type error: {e}")Salida:
Type error: can only concatenate str (not "int") to strFileNotFoundError
Se lanza cuando intenta abrir un archivo que no existe.
try:
with open("nonexistent_file.txt", "r") as f:
content = f.read()
except FileNotFoundError:
print("File not found. Check the file path.")KeyError
Se lanza cuando accede a una clave de diccionario que no existe.
data = {"name": "Alice", "age": 30}
try:
email = data["email"]
except KeyError as e:
print(f"Missing key: {e}")Salida:
Missing key: 'email'IndexError
Se lanza cuando accede a un índice de lista fuera de rango.
items = [10, 20, 30]
try:
value = items[5]
except IndexError:
print("Index out of range.")Múltiples bloques Except
Puede manejar diferentes tipos de excepciones con bloques except separados. Python los verifica en orden y ejecuta la primera coincidencia.
def parse_config(raw_value):
try:
parts = raw_value.split(":")
key = parts[0]
value = int(parts[1])
return {key: value}
except IndexError:
print("Config format error: missing colon separator.")
except ValueError:
print("Config format error: value is not a number.")
except AttributeError:
print("Config format error: input is not a string.")
parse_config("timeout:30") # Returns {'timeout': 30}
parse_config("timeout") # Config format error: missing colon separator.
parse_config("timeout:abc") # Config format error: value is not a number.
parse_config(12345) # Config format error: input is not a string.También puede capturar múltiples excepciones en un solo bloque except usando una tupla:
try:
value = int(input("Enter a number: "))
result = 100 / value
except (ValueError, ZeroDivisionError) as e:
print(f"Invalid input: {e}")El bloque Else
El bloque else se ejecuta solo cuando no ocurre ninguna excepción en el bloque try. Separa el código del "camino feliz" del código de manejo de errores.
try:
number = int("42")
except ValueError:
print("That is not a valid number.")
else:
print(f"Successfully parsed: {number}")Salida:
Successfully parsed: 42El bloque else es útil porque cualquier excepción lanzada dentro de él no es capturada por los bloques except anteriores. Esto evita silenciar accidentalmente errores en su código de ruta exitosa.
filename = "data.txt"
try:
f = open(filename, "r")
except FileNotFoundError:
print(f"File '{filename}' does not exist.")
else:
content = f.read()
f.close()
print(f"Read {len(content)} characters.")Si open() falla, el bloque except lo maneja. Si open() tiene éxito, el bloque else lee el archivo. Cualquier error durante f.read() no se captura aquí -- se propaga hacia arriba, que es el comportamiento correcto.
El bloque Finally
El bloque finally siempre se ejecuta, haya ocurrido una excepción o no. Es el lugar correcto para código de limpieza: cerrar archivos, liberar bloqueos, desconectar de bases de datos.
f = None
try:
f = open("data.txt", "r")
content = f.read()
except FileNotFoundError:
print("File not found.")
finally:
if f is not None:
f.close()
print("File handle closed.")El bloque finally se ejecuta incluso si el bloque try retorna un valor o lanza una excepción no manejada.
def divide(a, b):
try:
return a / b
except ZeroDivisionError:
return None
finally:
print("Division attempted.")
result = divide(10, 3)
# Output: Division attempted.
# result = 3.3333333333333335
result = divide(10, 0)
# Output: Division attempted.
# result = NoneEl patrón completo Try/Except/Else/Finally
Así es como funcionan los cuatro bloques juntos:
import json
def load_config(filepath):
"""Load and parse a JSON config file."""
f = None
try:
f = open(filepath, "r")
data = json.load(f)
except FileNotFoundError:
print(f"Config file '{filepath}' not found.")
return {}
except json.JSONDecodeError as e:
print(f"Invalid JSON in '{filepath}': {e}")
return {}
else:
print(f"Config loaded successfully with {len(data)} keys.")
return data
finally:
if f is not None:
f.close()
print("File handle released.")
config = load_config("settings.json")| Bloque | Cuándo se ejecuta | Propósito |
|---|---|---|
try | Siempre (primero) | Contiene el código que podría lanzar una excepción |
except | Solo cuando ocurre una excepción coincidente | Maneja el error |
else | Solo cuando no ocurre excepción en try | Ejecuta la lógica de éxito |
finally | Siempre (último) | Código de limpieza que debe ejecutarse sin importar qué |
Referencia de excepciones integradas comunes
Python incluye una jerarquía de excepciones integradas. Aquí están las que encontrará con mayor frecuencia.
| Excepción | Cuándo ocurre | Ejemplo |
|---|---|---|
Exception | Clase base para la mayoría de excepciones | Padre de todas las excepciones que no terminan el sistema |
ValueError | Valor incorrecto para el tipo correcto | int("abc") |
TypeError | Tipo incorrecto para una operación | "text" + 5 |
KeyError | Clave de diccionario faltante | d["missing"] |
IndexError | Índice de lista fuera de rango | [1,2,3][10] |
FileNotFoundError | El archivo no existe | open("no.txt") |
ZeroDivisionError | División o módulo por cero | 1 / 0 |
AttributeError | El objeto no tiene el atributo | None.append(1) |
ImportError | La importación del módulo falla | import nonexistent |
OSError | La operación del SO falla | Disco lleno, permiso denegado |
StopIteration | El iterador no tiene más elementos | next() en iterador agotado |
RuntimeError | Error genérico en tiempo de ejecución | Cajón de sastre para fallos diversos |
Todas estas heredan de BaseException. En la práctica, debería capturar Exception o sus subclases, nunca BaseException directamente (que incluye SystemExit y KeyboardInterrupt).
Lanzar excepciones con raise
Puede lanzar excepciones explícitamente cuando su código detecta condiciones inválidas. Así es como se imponen precondiciones y se señalan errores al llamador.
def set_age(age):
if not isinstance(age, int):
raise TypeError(f"Age must be an integer, got {type(age).__name__}")
if age < 0 or age > 150:
raise ValueError(f"Age must be between 0 and 150, got {age}")
return age
# Valid usage
print(set_age(25)) # 25
# Invalid usage
try:
set_age(-5)
except ValueError as e:
print(e) # Age must be between 0 and 150, got -5
try:
set_age("thirty")
except TypeError as e:
print(e) # Age must be an integer, got strTambién puede relanzar la excepción actual dentro de un bloque except usando raise sin argumentos. Esto es útil cuando desea registrar un error y luego dejarlo propagarse.
import logging
try:
result = 10 / 0
except ZeroDivisionError:
logging.error("Division by zero encountered")
raise # re-raises the original ZeroDivisionErrorClases de excepciones personalizadas
Para proyectos más grandes, defina sus propias clases de excepciones. Las excepciones personalizadas hacen que el manejo de errores sea más legible y permiten a los llamadores capturar modos de fallo específicos.
class ValidationError(Exception):
"""Raised when input data fails validation."""
def __init__(self, field, message):
self.field = field
self.message = message
super().__init__(f"Validation failed on '{field}': {message}")
class DatabaseConnectionError(Exception):
"""Raised when the database connection fails."""
pass
# Usage
def validate_email(email):
if "@" not in email:
raise ValidationError("email", "Must contain @ symbol")
if "." not in email.split("@")[1]:
raise ValidationError("email", "Domain must contain a dot")
return email
try:
validate_email("userexample.com")
except ValidationError as e:
print(e) # Validation failed on 'email': Must contain @ symbol
print(e.field) # email
print(e.message) # Must contain @ symbolLas excepciones personalizadas deben heredar de Exception, no de BaseException. Agrupe excepciones relacionadas bajo una clase base común para que los llamadores puedan capturar categorías amplias o específicas:
class AppError(Exception):
"""Base exception for this application."""
pass
class ConfigError(AppError):
pass
class NetworkError(AppError):
pass
# Caller can catch all app errors or specific ones
try:
raise NetworkError("Connection timed out")
except AppError as e:
print(f"Application error: {e}")Mejores prácticas para el manejo de excepciones en Python
1. Nunca capture excepciones genéricas
Un except: genérico captura todo, incluyendo KeyboardInterrupt y SystemExit. Esto oculta errores y convierte la depuración en una pesadilla.
# BAD - catches everything, hides real bugs
try:
do_something()
except:
pass
# GOOD - catches specific exceptions
try:
do_something()
except ValueError as e:
logging.warning(f"Invalid value: {e}")Si debe capturar un rango amplio, use except Exception en su lugar. Esto todavía permite que KeyboardInterrupt y SystemExit se propaguen.
2. Mantenga los bloques Try pequeños
Coloque solo el código que podría lanzar la excepción dentro del bloque try. Los bloques try grandes hacen que no quede claro qué línea causó el error.
# BAD - too much code in try
try:
data = load_data()
cleaned = clean_data(data)
result = analyze(cleaned)
save_results(result)
except Exception as e:
print(f"Something failed: {e}")
# GOOD - narrow try blocks
data = load_data()
cleaned = clean_data(data)
try:
result = analyze(cleaned)
except ValueError as e:
print(f"Analysis failed: {e}")
result = default_result()
save_results(result)3. Registre las excepciones, no las silencie
Tragarse las excepciones con pass crea errores invisibles. Siempre registre o reporte el error.
import logging
try:
process_record(record)
except ValueError as e:
logging.error(f"Failed to process record {record['id']}: {e}")4. Use tipos de excepciones específicos
Capture el tipo de excepción más específico posible. Esto evita manejar accidentalmente errores que no anticipó.
| Enfoque | Captura | Nivel de riesgo |
|---|---|---|
except: | Todo incluyendo SystemExit | Muy alto |
except Exception: | Todas las excepciones estándar | Alto |
except ValueError: | Solo ValueError | Bajo |
except (ValueError, TypeError): | Dos tipos específicos | Bajo |
5. Limpie recursos en Finally o use administradores de contexto
Para manejadores de archivos, conexiones de base de datos y bloqueos, siempre use finally o (mejor aún) una sentencia with.
# Prefer context managers for resource cleanup
with open("data.txt", "r") as f:
content = f.read()
# File is automatically closed, even if an exception occursEjemplos del mundo real
Leer y analizar un archivo JSON
import json
def read_json_config(filepath):
"""Read a JSON configuration file with proper error handling."""
try:
with open(filepath, "r") as f:
config = json.load(f)
except FileNotFoundError:
print(f"Config file not found: {filepath}")
return None
except PermissionError:
print(f"No permission to read: {filepath}")
return None
except json.JSONDecodeError as e:
print(f"Invalid JSON at line {e.lineno}, column {e.colno}: {e.msg}")
return None
else:
print(f"Loaded config with keys: {list(config.keys())}")
return config
config = read_json_config("app_config.json")
if config:
db_host = config.get("database_host", "localhost")Realizar llamadas a APIs HTTP
import urllib.request
import urllib.error
import json
def fetch_user(user_id):
"""Fetch user data from an API with retry logic."""
url = f"https://jsonplaceholder.typicode.com/users/{user_id}"
max_retries = 3
for attempt in range(1, max_retries + 1):
try:
with urllib.request.urlopen(url, timeout=5) as response:
data = json.loads(response.read().decode())
return data
except urllib.error.HTTPError as e:
if e.code == 404:
print(f"User {user_id} not found.")
return None
print(f"HTTP error {e.code} on attempt {attempt}")
except urllib.error.URLError as e:
print(f"Network error on attempt {attempt}: {e.reason}")
except json.JSONDecodeError:
print("API returned invalid JSON.")
return None
print(f"All {max_retries} attempts failed.")
return None
user = fetch_user(1)
if user:
print(f"Found user: {user['name']}")Procesamiento de datos CSV
import csv
def process_sales_data(filepath):
"""Process a CSV file with robust error handling."""
results = []
try:
with open(filepath, "r", newline="") as f:
reader = csv.DictReader(f)
for row_num, row in enumerate(reader, start=2):
try:
amount = float(row["amount"])
quantity = int(row["quantity"])
results.append({
"product": row["product"],
"total": amount * quantity,
})
except KeyError as e:
print(f"Row {row_num}: Missing column {e}")
except ValueError as e:
print(f"Row {row_num}: Invalid number - {e}")
except FileNotFoundError:
print(f"File not found: {filepath}")
except PermissionError:
print(f"Cannot read file: {filepath}")
return resultsTry/Except vs If/Else: Cuándo usar cada uno
Python sigue el principio EAFP: "Easier to Ask Forgiveness than Permission" (Es más fácil pedir perdón que permiso). Esto contrasta con el enfoque LBYL: "Look Before You Leap" (Mira antes de saltar).
# LBYL (Look Before You Leap) - using if/else
if "email" in user_data:
email = user_data["email"]
else:
email = "unknown"
# EAFP (Easier to Ask Forgiveness) - using try/except
try:
email = user_data["email"]
except KeyError:
email = "unknown"| Criterio | if/else (LBYL) | try/except (EAFP) |
|---|---|---|
| Mejor cuando | La verificación es barata y el error es común | El error es raro o la verificación es costosa |
| Rendimiento (sin error) | Ligeramente más lento (verificación extra cada vez) | Ligeramente más rápido (sin sobrecarga de verificación) |
| Rendimiento (ocurre error) | Igual | Más lento (la creación de excepciones tiene sobrecarga) |
| Condiciones de carrera | Posibles (el estado puede cambiar entre verificación y uso) | Ninguna (operación atómica) |
| Legibilidad | Clara para condiciones simples | Mejor para operaciones que pueden fallar de múltiples maneras |
| Operaciones de archivo | if os.path.exists(path) -- el archivo podría eliminarse entre la verificación y la apertura | try: open(path) -- maneja el fallo real |
| Acceso a diccionario | if key in dict -- simple y rápido | try: dict[key] -- o simplemente use dict.get(key, default) |
Use try/except cuando:
- El caso de fallo es raro (las excepciones están optimizadas para el camino "sin error").
- La verificación en sí es tan costosa como la operación (por ejemplo, verificar si un archivo existe y luego abrirlo).
- Múltiples cosas pueden salir mal en una secuencia de operaciones.
- Las condiciones de carrera son una preocupación (especialmente con archivos y recursos de red).
Use if/else cuando:
- La condición es barata de verificar y los fallos son comunes.
- Está validando la entrada del usuario antes de procesarla.
- La lógica se lee más claramente como un condicional.
Depurar excepciones más rápido con RunCell
Cuando trabaja en notebooks de Jupyter, las excepciones pueden interrumpir su flujo de análisis. Encuentra un KeyError en la fila 50.000 de un DataFrame, o un TypeError aparece tres celdas dentro de una pipeline. Rastrear la causa raíz significa desplazarse por tracebacks e inspeccionar variables manualmente.
RunCell (opens in a new tab) es un agente de IA que se ejecuta directamente dentro de Jupyter. Lee el traceback completo, inspecciona las variables en su alcance actual y sugiere una corrección en contexto. Así es como ayuda con el manejo de excepciones:
- Análisis de traceback. RunCell analiza la cadena de excepciones y señala qué variable u operación causó el fallo, incluso en llamadas a funciones anidadas.
- Sugerencias de corrección. En lugar de buscar en Stack Overflow, RunCell genera una celda de código corregida que puede ejecutar inmediatamente. Sabe si debe agregar un
try/except, corregir una conversión de tipo o manejar una clave faltante. - Verificaciones preventivas. RunCell puede escanear su código y señalar operaciones que probablemente lancen excepciones -- como acceder a claves de diccionario sin
.get(), o dividir sin verificar por cero -- antes de que ejecute la celda.
Dado que RunCell opera dentro de su entorno Jupyter existente, tiene acceso a sus datos y variables reales. Las sugerencias que proporciona son específicas para su situación, no consejos genéricos.
FAQ
¿Cuál es la diferencia entre try/except y try/catch en Python?
Python usa try/except, no try/catch. La sintaxis try/catch pertenece a lenguajes como Java, JavaScript y C++. En Python, la palabra clave es except. La funcionalidad es la misma: intenta código que podría fallar y define un manejador para el caso de fallo.
¿Puedo usar múltiples bloques except en Python?
Sí. Puede encadenar tantos bloques except como necesite, cada uno capturando un tipo de excepción diferente. Python los evalúa en orden y ejecuta el primer bloque coincidente. También puede capturar múltiples excepciones en un bloque usando una tupla: except (ValueError, TypeError) as e:.
¿Cuándo debo usar else con try/except?
Use el bloque else cuando tenga código que solo debe ejecutarse si el bloque try tuvo éxito. El principal beneficio es que las excepciones lanzadas en el bloque else no son capturadas por los bloques except anteriores, evitando que silencie accidentalmente errores no relacionados.
¿Se ejecuta finally siempre en Python?
Sí. El bloque finally se ejecuta ya sea que el bloque try se completó normalmente, lanzó una excepción manejada o lanzó una excepción no manejada. Incluso se ejecuta si el bloque try o except contiene una sentencia return. Las únicas excepciones son si el proceso de Python es terminado externamente o se llama a os._exit().
¿Cómo creo excepciones personalizadas en Python?
Cree una nueva clase que herede de Exception. Puede agregar atributos personalizados y sobrescribir el método __init__. Por ejemplo: class MyError(Exception): pass. Para casos más complejos, agregue campos como códigos de error o datos de contexto. Siempre herede de Exception, no de BaseException.
Conclusión
El try/except de Python es el mecanismo estándar para manejar errores en tiempo de ejecución. Le permite capturar excepciones específicas, ejecutar código de limpieza y mantener sus programas estables cuando las cosas salen mal. El patrón completo -- try/except/else/finally -- cubre cada escenario: intentar la operación, manejar fallos, ejecutar lógica de éxito y limpiar recursos.
Los principios clave son directos. Capture excepciones específicas, no amplias. Mantenga sus bloques try pequeños. Siempre registre o reporte errores en lugar de silenciarlos. Use finally o administradores de contexto para la limpieza. Lance excepciones significativas en su propio código con mensajes de error claros.
Ya sea que esté leyendo archivos, haciendo llamadas a APIs o procesando entrada de usuario, el manejo adecuado de excepciones es la diferencia entre un script que se bloquea a las 2 AM y uno que registra el error y sigue ejecutándose. Comience con lo básico -- un simple try/except alrededor de código que podría fallar -- y evolucione hacia jerarquías de excepciones personalizadas a medida que sus proyectos crecen.