Archivos y excepciones
La mayoría de los programas que hacen trabajo real interactúan con el sistema de archivos: leen una configuración, escriben resultados, cargan datos. Y cuando algo sale mal, Python lanza una excepción, una señal de que ocurrió algo inesperado. Este capítulo cubre ambos temas: cómo introducir y extraer datos de archivos, y cómo escribir código que maneje los errores con elegancia en lugar de fallar abruptamente.
Apertura de archivos
open() abre un archivo y devuelve un objeto desde el que puedes leer o en el que puedes escribir. Le indicas la ruta y qué quieres hacer con el archivo (leer, escribir o agregar). Siempre cierra un archivo cuando termines; la sentencia with lo hace automáticamente.
f = open("data.txt", "r") # "r" = lectura
content = f.read()
f.close()El "r" es el modo:
| Modo | Significado |
|---|---|
"r" | Lectura. El archivo debe existir. Modo por defecto. |
"w" | Escritura. Crea o sobrescribe el archivo. |
"a" | Agregar. Añade al final sin borrar. |
"x" | Crear. Falla si el archivo ya existe. |
"r+" | Lectura y escritura. |
"b" | Binario. Se agrega a cualquier modo: "rb", "wb". |
Llama siempre a .close() cuando termines. Olvidarlo deja el archivo bloqueado y puede causar corrupción de datos. La forma confiable de manejar esto es la sentencia with.
La sentencia with
with open(...) se encarga del archivo por ti, cerrándolo automáticamente cuando el bloque indentado termina, incluso si ocurre un error. Usa siempre 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 se cierra aquí, garantizado¿Qué hace with?
with es la sintaxis de administradores de contexto de Python. Llama por ti al código de configuración y liberación; en este caso, abrir y cerrar el archivo de manera confiable. No necesitas saber cómo funciona internamente. Solo úsalo con open().
Lectura de archivos
Tres métodos para leer. .read() carga el archivo completo como una sola cadena. .readline() lee una línea. Iterar directamente sobre el objeto archivo lee línea por línea, que es la forma más eficiente para archivos grandes, ya que no carga todo en memoria de una sola vez.
with open("data.txt", "r") as f:
content = f.read() # archivo completo como una sola 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 termina en "\n"Para archivos grandes, leer línea por línea es más eficiente que cargar todo de una vez:
with open("big_file.txt", "r") as f:
for line in f: # iterar el archivo directamente, eficiente en memoria
print(line.strip()) # strip() elimina el salto de línea finalIterar directamente sobre el objeto archivo (for line in f) es la forma más eficiente e idiomática de leer un archivo grande.
Escritura de archivos
El modo "w" sobrescribe el archivo por completo 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 varias 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 por completo 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 varias 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")Excepciones
Cuando Python se encuentra con un problema que no puede manejar, lanza una excepción: un error que describe qué salió mal y dónde. Si no la manejas, tu programa falla e imprime un traceback. La siguiente tabla muestra las excepciones más comunes con las que te encontrarás.
Excepciones comunes con las que te encontrarás:
| Excepción | Cuándo ocurre |
|---|---|
FileNotFoundError | open() no puede encontrar el archivo |
ValueError | La función recibe un valor del tipo correcto pero contenido incorrecto, p. ej. int("abc") |
TypeError | Tipo totalmente incorrecto, p. ej. "hello" + 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 en un bloque try el código que puede fallar. Si ocurre una excepción, el bloque except correspondiente la maneja en lugar de fallar. Sé específico sobre qué excepción capturas: capturar todo con un except: desnudo oculta bugs reales.
try:
value = int("abc")
except ValueError:
print("Eso no es un número válido")Sé específico sobre qué excepción capturas. Capturar todas las excepciones con un except: desnudo oculta bugs:
# mal, captura todo incluyendo errores de programación
try:
result = do_something()
except:
pass
# bien, solo captura lo que esperas y puedes manejar realmente
try:
result = do_something()
except FileNotFoundError:
print("Archivo no encontrado")Capturar múltiples excepciones
Puedes manejar diferentes tipos de error en bloques except separados, o capturar varios tipos en un solo bloque usando una tupla. La parte as e te da acceso al mensaje del 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 capturar múltiples en una tupla:
except (ValueError, ZeroDivisionError) as e:
print(f"Error de entrada: {e}")as e vincula el objeto de la excepción a un nombre para que puedas inspeccionar el mensaje.
else y finally
else se ejecuta solo si no ocurrió ninguna excepción. finally siempre se ejecuta, haya habido o no una excepción. finally es útil para tareas de limpieza que deben ocurrir sin importar qué.
try:
with open("data.txt") as f:
content = f.read()
except FileNotFoundError:
print("Archivo no encontrado, usando valores por defecto")
content = ""
else:
print("Archivo cargado con éxito")
finally:
print("Se terminó el intento de cargar el archivo") # siempre se ejecutafinally es más útil para tareas de limpieza (cerrar conexiones, liberar bloqueos) incluso cuando ya estás usando with para los archivos.
raise
Puedes lanzar excepciones tú mismo con raise. Así es como haces que tus funciones señalen problemas claramente a quienes las llaman, en lugar de devolver silenciosamente un valor incorrecto.
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / bEsto hace que tus funciones sean explícitas sobre lo que esperan y señalen los problemas claramente a quienes las llaman.
Clases de excepción personalizadas
Para programas más grandes, puedes definir tus propios tipos de excepción heredando de Exception. Esto permite que quienes llaman capturen tus errores específicos por separado de otros tipos de errores.
class InsufficientFundsError(Exception):
pass
class BankAccount:
def __init__(self, balance):
self.balance = balance
def withdraw(self, amount):
if amount > self.balance:
raise InsufficientFundsError(
f"Cannot withdraw {amount}, balance is {self.balance}"
)
self.balance -= amounttry:
account.withdraw(1000)
except InsufficientFundsError as e:
print(f"Transaction declined: {e}")JSON
JSON es el formato que todo el mundo 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 dict o list de Python. json.dump() escribe un dict o list de Python a un archivo como JSON.
Leer JSON desde un archivo:
import json
with open("config.json", "r") as f:
config = json.load(f) # parsea JSON a un dict/list de Python
print(config["setting"])Escribir JSON a un archivo:
import json
data = {"name": "Sofí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 tipos JSON a Python:
| JSON | Python |
|---|---|
object {} | dict |
array [] | list |
string "" | str |
| number | 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": "Sofía", "score": 87}')
# Python a cadena
text = json.dumps({"name": "Sofía", "score": 87}, indent=2)json.load() lee desde un objeto archivo. json.loads() (con una "s") lee desde una cadena.
En la práctica
Un patrón de guardar/cargar para un juego simple: escribir el estado a JSON, cargarlo de nuevo en la siguiente ejecución y recurrir a valores por defecto si aún no existe un archivo de guardado:
import json
SAVE_FILE = "save_game.json"
def save_game(player_data: dict) -> None:
with open(SAVE_FILE, "w") as f:
json.dump(player_data, f, indent=2)
print("Juego guardado.")
def load_game() -> dict:
try:
with open(SAVE_FILE, "r") as f:
return json.load(f)
except FileNotFoundError:
print("No se encontró archivo de guardado, empezando desde cero.")
return {"name": "Player", "score": 0, "level": 1}
state = load_game()
state["score"] += 50
save_game(state)
