Archivos y excepciones

La mayoría de los programas que hacen trabajo real tocan el sistema de archivos: leyendo una configuración, escribiendo resultados, cargando datos. Y cuando las cosas salen mal, Python levanta una excepción, una señal de que algo inesperado sucedió. Este capítulo cubre ambos: obtener datos dentro y fuera de archivos, y escribir código que maneja errores con gracia en lugar de colapsar.
Abriendo archivos
open() abre un archivo y devuelve un objeto del cual puedes leer o escribir. Le dices la ruta y qué quieres hacer con el archivo (leer, escribir, o añadir). Siempre cierra un archivo cuando termines; la declaración with hace esto automáticamente.
f = open("data.txt", "r") # "r" = leer
content = f.read()
f.close()El "r" es el modo:
| Modo | Significado |
|---|---|
"r" | Leer. El archivo debe existir. Modo predeterminado. |
"w" | Escribir. Crea o sobrescribe el archivo. |
"a" | Añadir. Agrega al final sin borrar. |
"x" | Crear. Falla si el archivo ya existe. |
"r+" | Leer y escribir. |
"b" | Binario. Añadir a cualquier modo: "rb", "wb". |
Siempre llama a .close() cuando termines. Olvidarlo deja el archivo abierto y puede perder datos que aún estaban esperando ser escritos. La forma confiable de manejar esto es la declaración with.
open(path, mode) te entrega un objeto de archivo, y el modo dice qué quieres hacer: "r" para leer, "w" para escribir (que borra lo que estaba allí), "a" para añadir al final. Una vez perdí un archivo con "w" cuando quería "a", así que visualiza el modo antes de escribirlo. La declaración with
with open(...) maneja el archivo para ti, cerrándolo automáticamente cuando el bloque indentado termina, incluso si ocurre un error. Siempre usa with open(...) en lugar de open()/close() manual. Es más seguro y es el estándar.
with open("data.txt", "r") as f:
content = f.read()
# f está cerrado aquí, garantizadowith es la sintaxis context manager de Python: ejecuta código de configuración y desmontaje para ti, aquí abriendo el archivo y cerrándolo de forma confiable. No necesitas saber cómo funciona internamente para usarlo con open().
with open(...) as f abre el archivo, te deja trabajar con f dentro del bloque indentado, luego lo cierra para ti cuando el bloque termina, incluso si algo sale mal a mitad del camino. Úsalo cada vez en lugar de llamar a `.close()` tú mismo, es una cosa menos para olvidar. Leyendo archivos
Tres métodos para leer. .read() carga el archivo completo como una cadena. .readline() lee una línea. Iterar directamente sobre el objeto de archivo lee línea por línea, que es el enfoque más eficiente para archivos grandes ya que no carga todo en memoria a la vez.
with open("data.txt", "r") as f:
content = f.read() # archivo completo como una cadena
with open("data.txt", "r") as f:
first_line = f.readline() # una línea a la vez
with open("data.txt", "r") as f:
lines = f.readlines() # lista de líneas, cada una terminando en "\n"Para archivos grandes, leer línea por línea es más eficiente que cargar todo a la vez:
with open("big_file.txt", "r") as f:
for line in f: # itera el archivo directamente, eficiente en memoria
print(line.strip()) # strip() elimina el salto de línea al finalIterar directamente sobre el objeto de archivo (for line in f) es la forma más eficiente de leer un archivo grande.
.read() te da el archivo completo como una cadena, .readline() te da una sola línea. Cuando el archivo podría ser grande, haz un bucle con for line in f en lugar de eso, lee una línea a la vez así nunca cargas todo en memoria. El `.strip()` en cada línea borra el salto de línea al final. Escribiendo archivos
El modo "w" sobrescribe el archivo completamente si existe. El modo "a" añade al final. .write() no agrega un salto de línea automáticamente; incluye "\n" explícitamente al final de cada línea. Para escribir múltiples líneas a la vez, únelas con "\n".join().
with open("output.txt", "w") as f:
f.write("Hola, mundo\n")
with open("output.txt", "a") as f:
f.write("Otra línea\n")"w" sobrescribe el archivo completamente si existe. "a" añade al final.
f.write() no agrega un salto de línea automáticamente, así que incluye "\n" explícitamente. Para escribir múltiples líneas a la vez:
lines = ["Línea uno", "Línea dos", "Línea tres"]
with open("output.txt", "w") as f:
f.write("\n".join(lines) + "\n")"w" crea o sobrescribe el archivo, "a" añade al final sin borrarlo. .write() no agrega un salto de línea para ti, así que pega `"\n"` al final de cada línea tú mismo. Para un lote de líneas, `"\n".join(lines)` las une primero. Excepciones
Cuando Python golpea un problema que no puede manejar, levanta una excepción: un error que describe qué salió mal y dónde. Si no la manejas, tu programa colapsa e imprime un traceback. La tabla abajo muestra las excepciones más comunes que encontrarás.
Las excepciones comunes que encontrarás:
| Excepción | Cuándo sucede |
|---|---|
FileNotFoundError | open() no puede encontrar el archivo |
ValueError | La función obtiene un valor del tipo correcto pero contenido incorrecto, p. ej. int("abc") |
TypeError | Tipo completamente incorrecto, p. ej. "hola" + 5 |
KeyError | La clave del diccionario no existe |
IndexError | Índice de lista fuera de rango |
ZeroDivisionError | División por cero |
AttributeError | El objeto no tiene ese atributo o método |
try / except
Envuelve el código que podría fallar en un bloque try. Si ocurre una excepción, el bloque except coincidente la maneja en lugar de colapsar. Sé específico sobre qué excepción atrapas: atrapar todo con una except: simple esconde errores reales.
try:
value = int("abc")
except ValueError:
print("Eso no es un número válido")Sé específico sobre qué excepción atrapas. Atrapar todas las excepciones con una except: simple esconde errores:
# malo, atrapa todo incluyendo errores del programador
try:
result = do_something()
except:
pass
# bueno, solo atrapa lo que esperas y puedes realmente manejar
try:
result = do_something()
except FileNotFoundError:
print("Archivo no encontrado")try, y el except coincidente se ejecuta en lugar de que el programa colapse. Nombra la excepción que esperas, como ValueError, en lugar de una except: simple, que atrapa todo y te oculta errores reales. Atrapa lo que realmente puedas manejar, y deja que el resto se vea. Atrapando múltiples excepciones
Puedes manejar diferentes tipos de error en bloques except separados, o atrapar varios tipos en un bloque usando una tupla. La parte as e te da acceso al mensaje de error.
try:
data = int(user_input)
result = 100 / data
except ValueError:
print("No es un número")
except ZeroDivisionError:
print("No se puede dividir por cero")O atrapa múltiples en una tupla:
except (ValueError, ZeroDivisionError) as e:
print(f"Error de entrada: {e}")as e vincula el objeto de excepción a un nombre así puedas inspeccionar el mensaje.
except, y Python ejecuta el primero que coincida. Para manejar algunos de la misma manera, lístalos en una tupla: except (ValueError, ZeroDivisionError). La parte as e te entrega el objeto de error así puedas imprimir su mensaje. else y finally
else se ejecuta solo si no ocurrió ninguna excepción. finally siempre se ejecuta, haya o no excepción. finally es útil para limpieza que debe suceder sin importar qué.
try:
with open("data.txt") as f:
content = f.read()
except FileNotFoundError:
print("Archivo no encontrado, usando predeterminados")
content = ""
else:
print("Archivo cargado exitosamente")
finally:
print("Terminado intentando cargar archivo") # siempre se ejecutafinally es más útil para limpieza (cerrar conexiones, liberar cerrojos) incluso cuando ya estés usando with para archivos.
else se ejecuta solo cuando el `try` no levantó nada, un lugar ordenado para el resto del camino del éxito. finally se ejecuta cada vez, excepción o no, que lo hace el lugar para limpieza que tiene que suceder sea cual sea el resultado. Con archivos `with` ya cubre la limpieza, así alcanzarás `finally` menos de lo que podrías esperar. raise
Puedes levantar excepciones tú mismo con raise. Así es cómo haces que tus funciones señalen problemas claramente a los llamadores en lugar de silenciosamente devolver un valor incorrecto.
def divide(a, b):
if b == 0:
raise ValueError("No se puede dividir por cero")
return a / bEsto hace que tus funciones sean explícitas sobre lo que esperan y señala problemas claramente a los llamadores.
raise ExceptionType("message") permite a tu propio código señalar un problema en voz alta en lugar de continuar con un valor malo. Es cómo una función dice "No puedo trabajar con esto" así el llamador lo maneja. Claro y temprano vence a una respuesta incorrecta que aparece mucho después. Clases de excepciones personalizadas
Para programas más grandes, puedes definir tus propios tipos de excepción heredando de Exception. Esto permite a los llamadores atrapar tus errores específicos separadamente de otros tipos de errores.
class SaldoInsuficienteError(Exception):
pass
class CuentaBancaria:
def __init__(self, balance):
self.balance = balance
def withdraw(self, amount):
if amount > self.balance:
raise SaldoInsuficienteError(
f"No se puede retirar {amount}, el balance es {self.balance}"
)
self.balance -= amounttry:
account.withdraw(1000)
except SaldoInsuficienteError as e:
print(f"Transacción rechazada: {e}")Exception, y una clase simple con cuerpo vacío es suficiente para empezar. Le da a los llamadores un nombre que es tuyo para atrapar, separado de errores incorporados, así los problemas de tu programa se destacan. JSON
JSON es el formato que todo habla: APIs, archivos de configuración, exportaciones de datos. El módulo json de Python lo maneja directamente. json.load() lee JSON desde un archivo a un diccionario o lista de Python. json.dump() escribe un diccionario o lista de vuelta a un archivo como JSON.
Leer JSON desde un archivo:
import json
with open("config.json", "r") as f:
config = json.load(f) # analiza JSON en un dict/list de Python
print(config["setting"])Escribir JSON a un archivo:
import json
data = {"name": "María", "score": 87, "active": True}
with open("output.json", "w") as f:
json.dump(data, f, indent=2) # indent= lo hace legible para humanosMapeo de tipo JSON a Python:
| JSON | Python |
|---|---|
objeto {} | dict |
arreglo [] | list |
cadena "" | str |
| número | int o float |
true / false | True / False |
null | None |
Para convertir entre cadenas JSON y objetos Python sin tocar un archivo:
import json
# cadena a Python
data = json.loads('{"name": "María", "score": 87}')
# Python a cadena
text = json.dumps({"name": "María", "score": 87}, indent=2)json.load() lee desde un objeto de archivo. json.loads() (con una "s") lee desde una cadena.
json.load(f) convierte JSON en un archivo en dicts y listas de Python, json.dump(data, f) los escribe de vuelta. Las versiones `s`, json.loads y json.dumps, hacen lo mismo para una cadena en lugar de un archivo. Añade indent=2 cuando quieras la salida legible. En la práctica
Un patrón de guardar/cargar para un pequeño juego: escribe estado a JSON, cárgalo de vuelta en la próxima ejecución, y vuelve a predeterminados si aún no existe archivo de guardado:
import json
ARCHIVO_GUARDADO = "save_game.json"
def save_game(player_data: dict) -> None:
with open(ARCHIVO_GUARDADO, "w") as f:
json.dump(player_data, f, indent=2)
print("Juego guardado.")
def load_game() -> dict:
try:
with open(ARCHIVO_GUARDADO, "r") as f:
return json.load(f)
except FileNotFoundError:
print("Archivo de guardado no encontrado, comenzando de fresco.")
return {"name": "Jugador", "score": 0, "level": 1}
state = load_game()
state["score"] += 50
save_game(state)
