Skip to content
Temas
Python
Python Try Except: How to Handle Exceptions the Right Way

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 / 0

Salida:

Traceback (most recent call last):
  File "example.py", line 2, in <module>
    result = 10 / 0
ZeroDivisionError: division by zero

Las 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 zero

Capturando 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 str

FileNotFoundError

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: 42

El 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 = None

El 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")
BloqueCuándo se ejecutaPropósito
trySiempre (primero)Contiene el código que podría lanzar una excepción
exceptSolo cuando ocurre una excepción coincidenteManeja el error
elseSolo cuando no ocurre excepción en tryEjecuta la lógica de éxito
finallySiempre (ú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ónCuándo ocurreEjemplo
ExceptionClase base para la mayoría de excepcionesPadre de todas las excepciones que no terminan el sistema
ValueErrorValor incorrecto para el tipo correctoint("abc")
TypeErrorTipo incorrecto para una operación"text" + 5
KeyErrorClave de diccionario faltanted["missing"]
IndexErrorÍndice de lista fuera de rango[1,2,3][10]
FileNotFoundErrorEl archivo no existeopen("no.txt")
ZeroDivisionErrorDivisión o módulo por cero1 / 0
AttributeErrorEl objeto no tiene el atributoNone.append(1)
ImportErrorLa importación del módulo fallaimport nonexistent
OSErrorLa operación del SO fallaDisco lleno, permiso denegado
StopIterationEl iterador no tiene más elementosnext() en iterador agotado
RuntimeErrorError genérico en tiempo de ejecuciónCajó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 str

Tambié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 ZeroDivisionError

Clases 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 @ symbol

Las 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ó.

EnfoqueCapturaNivel de riesgo
except:Todo incluyendo SystemExitMuy alto
except Exception:Todas las excepciones estándarAlto
except ValueError:Solo ValueErrorBajo
except (ValueError, TypeError):Dos tipos específicosBajo

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 occurs

Ejemplos 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 results

Try/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"
Criterioif/else (LBYL)try/except (EAFP)
Mejor cuandoLa verificación es barata y el error es comúnEl 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)IgualMás lento (la creación de excepciones tiene sobrecarga)
Condiciones de carreraPosibles (el estado puede cambiar entre verificación y uso)Ninguna (operación atómica)
LegibilidadClara para condiciones simplesMejor para operaciones que pueden fallar de múltiples maneras
Operaciones de archivoif os.path.exists(path) -- el archivo podría eliminarse entre la verificación y la aperturatry: open(path) -- maneja el fallo real
Acceso a diccionarioif key in dict -- simple y rápidotry: 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.

📚