Ir al contenido principal

Manejo de errores con archivos: bloque try-except

Introducción

Cuando trabajamos con archivos en Python, numerosas situaciones pueden generar errores: el archivo puede no existir, podemos no tener permisos suficientes, el disco puede estar lleno o el archivo podría estar bloqueado por otro proceso. Sin un manejo adecuado de estas situaciones, nuestros programas podrían terminar abruptamente o comportarse de manera inesperada. En este artículo, aprenderemos a utilizar el bloque try-except para manejar estos errores de forma elegante, lo que nos permitirá crear aplicaciones más robustas y profesionales que puedan recuperarse de situaciones problemáticas al trabajar con archivos.

Errores comunes al trabajar con archivos

Antes de ver cómo manejar los errores, vamos a entender qué tipos de errores pueden surgir:

Errores de acceso a archivos

# Intentar abrir un archivo que no existe
try:
    archivo = open("archivo_inexistente.txt", "r")
    contenido = archivo.read()
    archivo.close()
except FileNotFoundError:
    print("Error: El archivo no existe.")

Errores de permisos

# Intentar escribir en un archivo sin permisos
try:
    archivo = open("/etc/sistema_config.txt", "w")
    archivo.write("Nueva configuración")
    archivo.close()
except PermissionError:
    print("Error: No tienes permisos para escribir en este archivo.")

Errores de sintaxis en la ruta

# Errores en la especificación de la ruta
try:
    archivo = open("C:\\archivos\\:archivo*.txt", "r")  # Caracteres no válidos en Windows
    contenido = archivo.read()
    archivo.close()
except OSError as error:
    print(f"Error del sistema: {error}")

Errores de E/S (entrada/salida)

# Errores de E/S (como disco lleno, desconexión de red, etc.)
try:
    archivo = open("//servidor/compartido/datos.txt", "r")
    contenido = archivo.read()
    archivo.close()
except IOError as error:
    print(f"Error de E/S: {error}")

Estructura del bloque try-except

La estructura básica para manejar errores en Python es:

try:
    # Código que puede generar un error
    pass
except TipoDeError:
    # Código que se ejecuta si ocurre ese error
    pass
else:
    # Código que se ejecuta si no hay errores
    pass
finally:
    # Código que se ejecuta siempre, haya error o no
    pass

Aplicando la estructura a archivos

try:
    # Intentar abrir y leer un archivo
    archivo = open("datos.txt", "r")
    contenido = archivo.read()
    print(contenido)
except FileNotFoundError:
    # Se ejecuta si el archivo no existe
    print("Error: El archivo no se encontró")
except PermissionError:
    # Se ejecuta si no hay permisos suficientes
    print("Error: No tienes permisos para leer este archivo")
else:
    # Se ejecuta si todo va bien (opcional)
    print("Archivo leído correctamente")
finally:
    # Se ejecuta siempre, haya error o no (opcional)
    # Asegurarse de que el archivo se cierre si se abrió
    if 'archivo' in locals() and not archivo.closed:
        archivo.close()
        print("Archivo cerrado")

Manejo específico de excepciones para archivos

Capturar múltiples excepciones

Podemos capturar varios tipos de excepciones de forma separada:

try:
    archivo = open("config.txt", "r")
    configuracion = archivo.read()
    valores = configuracion.split("=")
    resultado = 100 / int(valores[1])
    archivo.close()
except FileNotFoundError:
    print("El archivo de configuración no existe.")
except IndexError:
    print("El formato del archivo de configuración es incorrecto.")
except ZeroDivisionError:
    print("El valor de configuración no puede ser cero.")
except ValueError:
    print("El valor de configuración debe ser un número.")

Capturar múltiples excepciones en una línea

También podemos agrupar excepciones:

try:
    archivo = open("datos.txt", "r")
    contenido = archivo.read()
    archivo.close()
except (FileNotFoundError, PermissionError) as error:
    print(f"Error al acceder al archivo: {error}")

Capturar cualquier excepción

Aunque no es una buena práctica, a veces necesitamos capturar cualquier error:

try:
    archivo = open("datos.txt", "r")
    contenido = archivo.read()
    archivo.close()
except Exception as error:
    print(f"Ocurrió un error inesperado: {error}")
    # Aquí podríamos registrar el error en un log

Patrón de uso con el gestor de contexto (with)

El patrón recomendado para trabajar con archivos es usar el gestor de contexto with, que se encarga automáticamente de cerrar el archivo:

try:
    with open("datos.txt", "r") as archivo:
        contenido = archivo.read()
        # El archivo se cierra automáticamente al salir del bloque with
except FileNotFoundError:
    print("El archivo no existe")

Ventajas del gestor de contexto

  • Cierra el archivo automáticamente, incluso si ocurre una excepción
  • Código más limpio y menos propenso a errores
  • Manejo de recursos más eficiente

Mejores prácticas para el manejo de errores con archivos

1. Utilizar siempre el gestor de contexto (with)

try:
    with open("datos.txt", "r") as archivo:
        contenido = archivo.read()
except FileNotFoundError:
    print("El archivo no existe")

2. Ser específico con las excepciones

# Malo: capturar todas las excepciones
try:
    with open("datos.txt", "r") as archivo:
        contenido = archivo.read()
except Exception:
    print("Ocurrió un error")  # Poco informativo

# Bueno: capturar excepciones específicas
try:
    with open("datos.txt", "r") as archivo:
        contenido = archivo.read()
except FileNotFoundError:
    print("El archivo no existe")
except PermissionError:
    print("No tienes permisos para acceder al archivo")

3. Proporcionar información útil sobre el error

try:
    ruta_archivo = "C:/usuarios/datos.txt"
    with open(ruta_archivo, "r") as archivo:
        contenido = archivo.read()
except FileNotFoundError:
    print(f"El archivo {ruta_archivo} no existe. Verifica la ruta.")
except PermissionError:
    print(f"No tienes permisos para acceder a {ruta_archivo}.")
except Exception as error:
    print(f"Error inesperado al leer {ruta_archivo}: {error}")

4. Registrar errores para depuración

import logging

# Configurar el sistema de logging
logging.basicConfig(
    filename='errores_app.log',
    level=logging.ERROR,
    format='%(asctime)s - %(levelname)s - %(message)s'
)

try:
    with open("datos_importantes.txt", "r") as archivo:
        contenido = archivo.read()
except Exception as error:
    # Mostrar mensaje al usuario
    print("No se pudo acceder al archivo de datos")
    # Registrar detalles técnicos en el log
    logging.error(f"Error al leer datos_importantes.txt: {error}", exc_info=True)

Ejemplos prácticos

Ejemplo 1: Lector de configuración robusto

def leer_configuracion(ruta_archivo):
    """Lee un archivo de configuración en formato clave=valor."""
    config = {}
    
    try:
        with open(ruta_archivo, "r") as archivo:
            for linea in archivo:
                linea = linea.strip()
                # Ignorar líneas vacías o comentarios
                if not linea or linea.startswith("#"):
                    continue
                    
                try:
                    clave, valor = linea.split("=", 1)
                    config[clave.strip()] = valor.strip()
                except ValueError:
                    print(f"Advertencia: Formato incorrecto en línea: {linea}")
                    
        return config
    except FileNotFoundError:
        print(f"El archivo de configuración {ruta_archivo} no existe.")
        return {}
    except PermissionError:
        print(f"No tienes permisos para leer {ruta_archivo}.")
        return {}
    except Exception as error:
        print(f"Error al leer la configuración: {error}")
        return {}

# Uso
configuracion = leer_configuracion("config.txt")
print("Configuración cargada:", configuracion)

Ejemplo 2: Copia de seguridad de archivos

import shutil
from pathlib import Path
import datetime

def hacer_backup(ruta_archivo):
    """Crea una copia de seguridad de un archivo con fecha y hora."""
    ruta = Path(ruta_archivo)
    
    # Verificar que el archivo existe
    if not ruta.exists():
        print(f"Error: El archivo {ruta.name} no existe.")
        return False
    
    # Crear nombre para la copia de seguridad
    fecha_hora = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
    nombre_backup = f"{ruta.stem}_{fecha_hora}{ruta.suffix}"
    ruta_backup = ruta.parent / "backups" / nombre_backup
    
    try:
        # Asegurar que existe el directorio de backups
        carpeta_backup = ruta.parent / "backups"
        carpeta_backup.mkdir(exist_ok=True)
        
        # Copiar el archivo
        shutil.copy2(ruta, ruta_backup)
        print(f"Backup creado: {ruta_backup}")
        return True
    except PermissionError:
        print(f"Error: No tienes permisos para crear el backup.")
        return False
    except shutil.SameFileError:
        print(f"Error: El archivo de origen y destino son el mismo.")
        return False
    except OSError as error:
        print(f"Error del sistema: {error}")
        return False

# Uso
# hacer_backup("documento_importante.txt")

Ejemplo 3: Verificador de archivos

from pathlib import Path

def verificar_archivos(lista_archivos):
    """Verifica que los archivos existan y sean accesibles."""
    resultados = []
    
    for ruta in lista_archivos:
        archivo = Path(ruta)
        estado = {
            "ruta": str(archivo),
            "nombre": archivo.name,
            "existe": False,
            "es_archivo": False,
            "tamaño": 0,
            "permisos": {
                "lectura": False,
                "escritura": False
            },
            "error": None
        }
        
        try:
            # Verificar existencia
            if archivo.exists():
                estado["existe"] = True
                
                # Verificar si es archivo
                if archivo.is_file():
                    estado["es_archivo"] = True
                    
                    # Obtener tamaño
                    estado["tamaño"] = archivo.stat().st_size
                    
                    # Verificar permisos
                    try:
                        # Prueba de lectura
                        with open(archivo, "r") as f:
                            f.read(1)  # Leer solo un byte para verificar
                        estado["permisos"]["lectura"] = True
                    except PermissionError:
                        pass
                    
                    try:
                        # Prueba de escritura (sin modificar el archivo)
                        with open(archivo, "a") as f:
                            pass
                        estado["permisos"]["escritura"] = True
                    except PermissionError:
                        pass
        except Exception as error:
            estado["error"] = str(error)
        
        resultados.append(estado)
    
    return resultados

# Uso
# archivos = ["datos1.txt", "datos2.txt", "carpeta/datos3.txt"]
# resultados = verificar_archivos(archivos)
# for resultado in resultados:
#     print(f"Archivo: {resultado['nombre']}")
#     print(f"  Existe: {resultado['existe']}")
#     if resultado['existe'] and resultado['es_archivo']:
#         print(f"  Tamaño: {resultado['tamaño']} bytes")
#         print(f"  Permiso lectura: {resultado['permisos']['lectura']}")
#         print(f"  Permiso escritura: {resultado['permisos']['escritura']}")
#     if resultado['error']:
#         print(f"  Error: {resultado['error']}")

Resumen

El manejo adecuado de errores al trabajar con archivos es esencial para desarrollar aplicaciones robustas. En este artículo, hemos aprendido a utilizar el bloque try-except para capturar y manejar distintos tipos de excepciones que pueden surgir al manipular archivos. Hemos visto cómo proporcionar información útil al usuario, registrar errores para depuración y seguir buenas prácticas como el uso del gestor de contexto with. También hemos explorado ejemplos prácticos que demuestran cómo implementar un manejo de errores profesional en situaciones cotidianas.

En el siguiente artículo, aprenderemos a trabajar con archivos CSV, un formato ampliamente utilizado para el intercambio de datos tabulares, y veremos cómo aplicar los conocimientos adquiridos hasta ahora para procesar este tipo de archivos de manera eficiente.