================================== Manejo de Archivos y Excepciones ================================== En esta sección aprenderemos sobre el manejo de archivos y excepciones en Python, dos conceptos fundamentales para crear programas robustos y trabajar con datos externos. Manejo de Archivos ------------------ Python proporciona funciones integradas para trabajar con archivos. Los archivos se pueden abrir para lectura, escritura o ambos. Apertura de Archivos ^^^^^^^^^^^^^^^^^^^ La función ``open()`` se utiliza para abrir archivos. Su sintaxis básica es: .. code-block:: python file = open(nombre_archivo, modo) Modos de Apertura """""""""""""""" Existen dos categorías principales de modos: texto y binario. Modos de Texto ''''''''''''' Los archivos de texto se manejan como cadenas de caracteres (strings): * ``'r'`` - Lectura (modo por defecto) * ``'w'`` - Escritura (crea nuevo archivo o sobrescribe si existe) * ``'a'`` - Append (añade al final del archivo) * ``'r+'`` - Lectura y escritura Modos Binarios '''''''''''' Para archivos binarios (imágenes, PDF, etc.), se añade 'b' al modo: * ``'rb'`` - Lectura binaria * ``'wb'`` - Escritura binaria * ``'ab'`` - Append binario * ``'r+b'`` - Lectura y escritura binaria Diferencias entre Modo Texto y Binario """""""""""""""""""""""""""""""""""" 1. **Modo Texto**: * Los datos se manejan como strings * Realiza conversiones de caracteres especiales (\\n, \\r\\n) * Codificación automática (UTF-8 por defecto) * Ideal para archivos .txt, .csv, .py, etc. .. code-block:: python # Ejemplo modo texto with open('archivo.txt', 'r') as f: texto = f.read() # Lee como string 1. **Modo Binario**: * Los datos se manejan como bytes * No realiza conversiones de caracteres * No aplica codificación * Necesario para archivos no textuales .. code-block:: python # Ejemplo modo binario with open('imagen.jpg', 'rb') as f: datos = f.read() # Lee como bytes Ejemplos Prácticos """""""""""""""" .. code-block:: python # Copiando una imagen (modo binario) with open('original.jpg', 'rb') as origen: with open('copia.jpg', 'wb') as destino: destino.write(origen.read()) # Trabajando con texto (modo texto) with open('notas.txt', 'w') as f: f.write('Hola\nMundo') # \n se convierte automáticamente al separador de línea del sistema # Trabajando con CSV (modo texto) with open('datos.csv', 'r') as f: for linea in f: campos = linea.strip().split(',') Lectura de Archivos ^^^^^^^^^^^^^^^^^^ Python ofrece varios métodos para leer archivos, cada uno con sus propias características y casos de uso específicos. Método read() """""""""""" El método ``read()`` lee todo el contenido del archivo como una única cadena de texto: .. code-block:: python # Leer todo el archivo de una vez with open('archivo.txt', 'r') as f: contenido = f.read() print(contenido) # Muestra todo el contenido **Ventajas**: * Simple y directo * Útil para archivos pequeños **Desventajas**: * Puede consumir mucha memoria con archivos grandes * No es eficiente para procesamiento línea por línea Iteración Línea por Línea """""""""""""""""""""""" Python permite iterar directamente sobre el archivo, leyendo una línea a la vez: .. code-block:: python # Leer línea por línea with open('archivo.txt', 'r') as f: for linea in f: # strip() elimina espacios en blanco y saltos de línea print(linea.strip()) **Ventajas**: * Eficiente en memoria * Ideal para archivos grandes * Permite procesar el archivo secuencialmente **Desventajas**: * No permite acceso aleatorio a las líneas Método readlines() """""""""""""""" El método ``readlines()`` lee todas las líneas del archivo y las devuelve como una lista: .. code-block:: python # Leer todas las líneas en una lista with open('archivo.txt', 'r') as f: lineas = f.readlines() # Cada elemento de la lista es una línea del archivo for linea in lineas: print(linea.strip()) **Ventajas**: * Permite acceso aleatorio a las líneas * Útil cuando necesitas manipular las líneas en cualquier orden * Facilita operaciones como ordenamiento o filtrado **Desventajas**: * Consume más memoria que la lectura línea por línea * No recomendado para archivos muy grandes Ejemplo Práctico Comparativo """"""""""""""""""""""""""" .. code-block:: python # Ejemplo que muestra diferentes formas de leer un archivo def leer_archivo_diferentes_formas(nombre_archivo): # Método 1: read() print("Usando read():") with open(nombre_archivo, 'r') as f: contenido = f.read() print(f"Contenido completo: {contenido}\n") # Método 2: iteración línea por línea print("Usando iteración línea por línea:") with open(nombre_archivo, 'r') as f: for i, linea in enumerate(f, 1): print(f"Línea {i}: {linea.strip()}") print() # Método 3: readlines() print("Usando readlines():") with open(nombre_archivo, 'r') as f: lineas = f.readlines() print(f"Número total de líneas: {len(lineas)}") for i, linea in enumerate(lineas, 1): print(f"Línea {i}: {linea.strip()}") Consideraciones Adicionales """"""""""""""""""""""""" 1. **Manejo de Archivos Grandes**: * Para archivos grandes, usar iteración línea por línea * Evitar ``read()`` y ``readlines()`` con archivos muy grandes 2. **Codificación**: * Especificar la codificación al abrir el archivo si es necesario: .. code-block:: python with open('archivo.txt', 'r', encoding='utf-8') as f: contenido = f.read() 3. **Memoria**: * ``read()`` y ``readlines()`` cargan todo el contenido en memoria * La iteración línea por línea es más eficiente en memoria 4. **Rendimiento**: * La iteración línea por línea es generalmente más rápida para procesamiento secuencial * ``readlines()`` es mejor cuando necesitas acceso aleatorio a las líneas Escritura en Archivos ^^^^^^^^^^^^^^^^^^^^ Python proporciona varios métodos para escribir en archivos. Es importante entender las diferentes opciones y sus implicaciones. Método write() """"""""""""" El método ``write()`` permite escribir una cadena de texto en el archivo: .. code-block:: python # Escribir una cadena simple with open('archivo.txt', 'w') as f: f.write('Hola Mundo\n') # \n añade un salto de línea # Escribir múltiples cadenas con write() with open('archivo.txt', 'w') as f: f.write('Primera línea\n') f.write('Segunda línea\n') f.write('Tercera línea\n') **Características**: * Escribe exactamente lo que se le pasa * No añade automáticamente saltos de línea * Retorna el número de caracteres escritos Método writelines() """"""""""""""""" El método ``writelines()`` permite escribir una secuencia de cadenas: .. code-block:: python # Escribir una lista de líneas lineas = ['Línea 1\n', 'Línea 2\n', 'Línea 3\n'] with open('archivo.txt', 'w') as f: f.writelines(lineas) # Usando comprensión de lista para añadir saltos de línea lineas = ['Línea 1', 'Línea 2', 'Línea 3'] with open('archivo.txt', 'w') as f: f.writelines(linea + '\n' for linea in lineas) **Características**: * Acepta cualquier iterable de strings * No añade separadores automáticamente * Más eficiente para escribir múltiples líneas Modos de Escritura """""""""""""""" 1. **Modo 'w' (write)**: * Crea un nuevo archivo o sobrescribe si existe * Borra todo el contenido previo .. code-block:: python with open('archivo.txt', 'w') as f: f.write('Nuevo contenido') # Sobrescribe todo el archivo 2. **Modo 'a' (append)**: * Añade contenido al final del archivo * Mantiene el contenido existente .. code-block:: python with open('archivo.txt', 'a') as f: f.write('\nContenido adicional') # Añade al final Ejemplos Prácticos """""""""""""""" 1. **Escribir datos estructurados**: .. code-block:: python # Escribir datos en formato específico datos = [ {'nombre': 'Ana', 'edad': 25}, {'nombre': 'Juan', 'edad': 30} ] with open('personas.txt', 'w') as f: for persona in datos: f.write(f"Nombre: {persona['nombre']}, Edad: {persona['edad']}\n") 2. **Combinar write() y writelines()**: .. code-block:: python with open('reporte.txt', 'w') as f: # Escribir encabezado f.write("REPORTE DIARIO\n") f.write("=============\n\n") # Escribir contenido items = ['Item 1', 'Item 2', 'Item 3'] f.writelines(f"- {item}\n" for item in items) Consideraciones Importantes """""""""""""""""""""""" 1. **Manejo de Codificación**: * Especificar la codificación al abrir el archivo: .. code-block:: python with open('archivo.txt', 'w', encoding='utf-8') as f: f.write('Texto con caracteres especiales: áéíóú') 2. **Buenas Prácticas**: * Siempre usar ``with`` para garantizar que el archivo se cierre * Verificar permisos de escritura * Hacer copias de seguridad antes de sobrescribir archivos importantes 3. **Rendimiento**: * ``writelines()`` es más eficiente para múltiples líneas * Para archivos grandes, considerar escribir en bloques * Evitar abrir y cerrar el archivo repetidamente 4. **Plataformas**: * Los saltos de línea pueden variar según el sistema operativo * Usar ``os.linesep`` para compatibilidad multiplataforma El Administrador de Contexto (with) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ En Python, existen dos formas principales de trabajar con archivos: usando el administrador de contexto ``with`` o manejando manualmente la apertura y cierre de archivos. Uso del Administrador de Contexto (with) """"""""""""""""""""""""""""""""""""""" El administrador de contexto ``with`` se encarga automáticamente de cerrar el archivo cuando terminamos de usarlo: .. code-block:: python # Uso recomendado con with with open('archivo.txt', 'r') as f: contenido = f.read() # Al salir del bloque with, el archivo se cierra automáticamente **Ventajas**: * Cierre automático del archivo * Código más limpio y seguro * Manejo automático de recursos Manejo Manual de Archivos """""""""""""""""""""""" Sin usar ``with``, debemos abrir y cerrar el archivo manualmente: .. code-block:: python # Método no recomendado f = open('archivo.txt', 'r') contenido = f.read() f.close() # Debemos recordar cerrar el archivo **Desventajas**: * Requiere cerrar el archivo explícitamente * Puede olvidarse cerrar el archivo * Más propenso a errores Ejemplos Comparativos """"""""""""""""""" 1. **Lectura Simple**: .. code-block:: python # Con with (recomendado) with open('datos.txt', 'r') as archivo: datos = archivo.read() # El archivo se cerrará automáticamente # Sin with (no recomendado) archivo = open('datos.txt', 'r') datos = archivo.read() archivo.close() # No olvidar cerrar 2. **Procesamiento de Múltiples Líneas**: .. code-block:: python # Con with with open('numeros.txt', 'r') as archivo: for linea in archivo: print(int(linea)) # Archivo se cierra automáticamente # Sin with archivo = open('numeros.txt', 'r') for linea in archivo: print(int(linea)) archivo.close() # Debemos recordar cerrar 3. **Escritura de Datos**: .. code-block:: python # Con with with open('salida.txt', 'w') as archivo: archivo.write('Línea 1\n') archivo.write('Línea 2\n') # Se cierra automáticamente # Sin with archivo = open('salida.txt', 'w') archivo.write('Línea 1\n') archivo.write('Línea 2\n') archivo.close() # No olvidar cerrar Situaciones donde es Necesario el Manejo Manual """""""""""""""""""""""""""""""""""""""""""" Aunque ``with`` es generalmente la mejor opción, hay situaciones donde el manejo manual puede ser necesario: 1. **Mantener un archivo abierto por largo tiempo**: .. code-block:: python # Archivo de registro que se mantiene abierto log_file = open('registro.txt', 'a') def registrar_evento(evento): log_file.write(f"{evento}\n") log_file.flush() # Forzar escritura # Usar el archivo... registrar_evento("Inicio de programa") registrar_evento("Operación completada") # Cerrar al finalizar el programa log_file.close() 2. **Múltiples operaciones en diferentes partes del código**: .. code-block:: python archivo = open('datos.txt', 'r') def procesar_parte1(): # Leer primeras 10 líneas for _ in range(10): print(archivo.readline()) def procesar_parte2(): # Leer las siguientes 5 líneas for _ in range(5): print(archivo.readline()) procesar_parte1() procesar_parte2() archivo.close() Buenas Prácticas """""""""""""" 1. Usar ``with`` siempre que sea posible 2. Si se maneja el archivo manualmente: * Cerrar el archivo tan pronto como sea posible * Usar ``flush()`` cuando sea necesario forzar la escritura * Documentar por qué se eligió el manejo manual 3. Considerar el uso de ``with`` en funciones separadas si se necesita procesar el archivo en partes Manejo de Excepciones -------------------- ¿Qué es una Excepción? ^^^^^^^^^^^^^^^^^^^^^ Una excepción es un evento que ocurre durante la ejecución de un programa que interrumpe el flujo normal de las instrucciones. En Python, las excepciones son una forma de manejar errores y situaciones inesperadas de manera controlada. Try-Except Básico ^^^^^^^^^^^^^^^ La estructura básica try-except permite ejecutar código que podría generar un error y manejar ese error de forma elegante: .. code-block:: python # Ejemplo 1: Conversión de tipos try: numero = int(input("Ingrese un número: ")) print(f"El número ingresado es: {numero}") except ValueError: print("Error: Debe ingresar un número válido") # Ejemplo 2: División try: numerador = 10 denominador = int(input("Ingrese el denominador: ")) resultado = numerador / denominador print(f"El resultado es: {resultado}") except ZeroDivisionError: print("Error: No se puede dividir entre cero") except ValueError: print("Error: Debe ingresar un número válido") Múltiples Excepciones ^^^^^^^^^^^^^^^^^^^ Python permite manejar diferentes tipos de excepciones de manera específica: .. code-block:: python def leer_numero_desde_archivo(nombre_archivo): try: with open(nombre_archivo, 'r') as archivo: numero = int(archivo.readline()) resultado = 100 / numero return resultado except FileNotFoundError: print(f"Error: No se encontró el archivo '{nombre_archivo}'") except ValueError: print("Error: El contenido del archivo no es un número") except ZeroDivisionError: print("Error: El número leído es cero y no se puede dividir") except Exception as e: print(f"Error inesperado: {str(e)}") return None Try-Except-Else-Finally ^^^^^^^^^^^^^^^^^^^^^^ La estructura completa de manejo de excepciones incluye cuatro bloques: 1. **try**: Código que puede generar una excepción 2. **except**: Maneja la excepción si ocurre 3. **else**: Se ejecuta si no hay excepciones 4. **finally**: Se ejecuta siempre, haya o no excepciones Estructura Básica: .. code-block:: python def procesar_archivo(nombre_archivo): try: with open(nombre_archivo, 'r') as archivo: contenido = archivo.read() except FileNotFoundError: print("El archivo no existe") return None else: print("Archivo leído exitosamente") return contenido finally: print("Proceso de archivo finalizado") Orden de Ejecución con Finally """""""""""""""""""""""""""" El bloque ``finally`` está diseñado para ejecutar código de limpieza y se ejecuta SIEMPRE, incluso si hay un return en los bloques anteriores: .. code-block:: python def ejemplo_orden_ejecucion(): archivo = None try: archivo = open('datos.txt', 'r') datos = archivo.read() return datos except FileNotFoundError: print("No se encontró el archivo") return None finally: if archivo: archivo.close() print("Archivo cerrado") # Si el archivo existe: # 1. Abre el archivo # 2. Lee los datos # 3. Ejecuta finally y cierra el archivo # 4. Retorna los datos # Si el archivo no existe: # 1. Captura FileNotFoundError # 2. Imprime mensaje de error # 3. Ejecuta finally (no hay archivo que cerrar) # 4. Retorna None Uso Típico de Finally """"""""""""""""""" El bloque ``finally`` se usa principalmente para tareas de limpieza: .. code-block:: python def procesar_datos(): conexion = None try: conexion = abrir_conexion() return procesar_informacion(conexion) except ConexionError: print("Error en la conexión") return None finally: if conexion: conexion.cerrar() # Siempre cierra la conexión Consideraciones Importantes """""""""""""""""""""""" 1. El bloque ``finally`` siempre se ejecuta, incluso si hay returns en ``try``, ``except`` o ``else`` 2. Se usa principalmente para limpieza de recursos 3. No debe contener lógica de negocio compleja 4. Es ideal para: * Cerrar archivos * Cerrar conexiones de red * Liberar recursos del sistema * Registrar fin de operaciones Excepciones Comunes y Sus Casos de Uso ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 1. **ValueError** Ocurre cuando una operación recibe un argumento con el tipo correcto pero valor inapropiado: .. code-block:: python def procesar_edad(): try: edad = int(input("Ingrese su edad: ")) if edad < 0: raise ValueError("La edad no puede ser negativa") return edad except ValueError as e: print(f"Error: {str(e)}") return None 2. **TypeError** Ocurre cuando se intenta realizar una operación con tipos de datos incompatibles: .. code-block:: python def sumar_valores(a, b): try: resultado = a + b return resultado except TypeError: print(f"Error: No se pueden sumar {type(a)} y {type(b)}") return None # Ejemplos de uso: print(sumar_valores(5, "3")) # Error print(sumar_valores(5, 3)) # 8 3. **FileNotFoundError** Ocurre cuando se intenta acceder a un archivo que no existe: .. code-block:: python def leer_configuracion(archivo_config): try: with open(archivo_config, 'r') as f: return f.read() except FileNotFoundError: print(f"El archivo {archivo_config} no existe") return None 4. **IndexError** Ocurre al intentar acceder a un índice que está fuera de rango: .. code-block:: python def obtener_elemento(lista, indice): try: return lista[indice] except IndexError: print(f"Error: El índice {indice} está fuera de rango") return None 5. **KeyError** Ocurre al intentar acceder a una clave que no existe en un diccionario: .. code-block:: python def obtener_valor(diccionario, clave): try: return diccionario[clave] except KeyError: print(f"Error: La clave '{clave}' no existe") return None Ejemplos Prácticos Integrados ^^^^^^^^^^^^^^^^^^^^^^^^^^ 1. **Procesamiento de Archivo con Números** .. code-block:: python def procesar_archivo_numeros(nombre_archivo): total = 0 numeros_procesados = 0 try: with open(nombre_archivo, 'r') as archivo: for numero_linea, linea in enumerate(archivo, 1): try: numero = float(linea.strip()) total += numero numeros_procesados += 1 except ValueError: print(f"Advertencia: Línea {numero_linea} no contiene un número válido") if numeros_procesados == 0: return 0 return total / numeros_procesados except FileNotFoundError: print(f"Error: No se encontró el archivo '{nombre_archivo}'") return None finally: print(f"Se procesaron {numeros_procesados} números válidos") 2. **Validación de Datos de Usuario** .. code-block:: python def validar_usuario(datos_usuario): campos_requeridos = ['nombre', 'edad', 'email'] try: # Verificar campos requeridos for campo in campos_requeridos: if campo not in datos_usuario: raise KeyError(f"Falta el campo {campo}") # Validar edad edad = int(datos_usuario['edad']) if edad < 0 or edad > 120: raise ValueError("Edad fuera de rango válido") # Validar email (ejemplo simple) if '@' not in datos_usuario['email']: raise ValueError("Email inválido") return True except KeyError as e: print(f"Error de datos: {str(e)}") except ValueError as e: print(f"Error de validación: {str(e)}") except Exception as e: print(f"Error inesperado: {str(e)}") return False Mejores Prácticas ^^^^^^^^^^^^^^^ 1. **Especificidad en las Excepciones** * Capturar excepciones específicas en lugar de usar except general * Ordenar las excepciones de más específicas a más generales .. code-block:: python # Mal try: # código except Exception as e: print(e) # Bien try: # código except ValueError: print("Error de valor") except TypeError: print("Error de tipo") except Exception as e: print(f"Error inesperado: {e}") 2. **Uso Apropiado de Finally** * Usar finally para limpieza de recursos * No colocar lógica compleja en finally * Recordar que finally se ejecuta antes de cualquier return 3. **Manejo de Recursos** * Preferir el uso de 'with' para recursos como archivos * Asegurar que los recursos se liberen adecuadamente 4. **Mensajes de Error** * Proporcionar mensajes claros y útiles * Incluir información relevante para la depuración 5. **Alcance del Try** * Minimizar el código dentro del bloque try * Incluir solo el código que puede generar la excepción específica 6. **Documentación** * Documentar las excepciones que puede lanzar una función * Explicar el significado y manejo de cada excepción Lanzamiento de Excepciones con Raise ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Python permite lanzar excepciones de forma explícita usando el comando ``raise``. Esto es útil cuando queremos indicar que ha ocurrido un error en nuestro código. Uso Básico de Raise """"""""""""""""" El comando ``raise`` permite lanzar cualquiera de las excepciones incorporadas de Python: .. code-block:: python def dividir(a, b): if b == 0: raise ValueError("No se puede dividir entre cero") return a / b # Uso try: resultado = dividir(10, 0) except ValueError as error: print(f"Error: {error}") Casos Comunes de Uso """""""""""""""""" 1. **Validación de Datos**: .. code-block:: python def validar_edad(edad): if edad < 0: raise ValueError("La edad no puede ser negativa") if edad > 120: raise ValueError("Edad no válida") return True # Uso try: validar_edad(-5) except ValueError as error: print(f"Error: {error}") 2. **Validación de Parámetros**: .. code-block:: python def procesar_lista(lista): if not lista: raise ValueError("La lista no puede estar vacía") if len(lista) > 100: raise ValueError("La lista es demasiado larga") return sum(lista) 3. **Manejo de Estados Inválidos**: .. code-block:: python def actualizar_saldo(saldo, monto): if saldo < 0: raise ValueError("El saldo no puede ser negativo") if monto < 0: raise ValueError("El monto no puede ser negativo") return saldo + monto Relanzamiento de Excepciones """"""""""""""""""""""""" A veces queremos capturar una excepción, hacer algo y luego relanzarla: .. code-block:: python def procesar_dato(valor): try: numero = int(valor) if numero < 0: raise ValueError("Número negativo no permitido") except ValueError as e: print(f"Ocurrió un error al procesar {valor}") raise # Relanza la última excepción Cuándo Usar Raise """"""""""""""" Es apropiado usar ``raise`` cuando: 1. Los datos o parámetros no cumplen con los requisitos esperados 2. El programa está en un estado inválido 3. No se pueden cumplir las precondiciones de una operación 4. Se detectan valores fuera de rango Ejemplos Prácticos """""""""""""""" 1. **Validación de Archivo**: .. code-block:: python def leer_configuracion(nombre_archivo): if not nombre_archivo.endswith('.txt'): raise ValueError("El archivo debe ser .txt") try: with open(nombre_archivo, 'r') as archivo: return archivo.read() except FileNotFoundError: raise ValueError(f"No se encontró el archivo {nombre_archivo}") 2. **Validación de Datos de Usuario**: .. code-block:: python def validar_usuario(nombre, edad): if not nombre: raise ValueError("El nombre no puede estar vacío") try: edad = int(edad) if edad < 0 or edad > 120: raise ValueError("Edad fuera de rango") except ValueError: raise ValueError("La edad debe ser un número válido") return True Buenas Prácticas """""""""""""" 1. **Mensajes Claros**: * Proporcionar mensajes de error descriptivos * Incluir detalles relevantes en el mensaje 2. **Tipo Apropiado**: * Usar el tipo de excepción más específico posible * ValueError para errores de valor * TypeError para errores de tipo * RuntimeError para errores de ejecución generales 3. **Documentación**: * Documentar las excepciones que puede lanzar una función * Explicar las condiciones que causan cada excepción