Archivos y excepciones
La mayoría de los programas que hacen algo útil 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 mover datos hacia y desde archivos, y cómo escribir código que maneje errores con elegancia en lugar de fallar de golpe.
Abrir archivos
open() abre un archivo y devuelve un objeto desde el cual puedes leer o escribir. Le indicas la ruta y qué quieres hacer con el archivo (leer, escribir o agregar contenido). Siempre cierra un archivo cuando termines; la instrucción with hace esto 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 predeterminado. |
"w" | Escritura. Crea o sobrescribe el archivo. |
"a" | Agregar (append). 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". |
Siempre llama a .close() cuando termines. Olvidarlo deja el archivo abierto y puede provocar la pérdida de datos que todavía estaban esperando a ser escritos. La forma confiable de manejar esto es la instrucción with.
open(path, mode) te entrega un objeto de archivo, y el modo indica qué quieres hacer: "r" para leer, "w" para escribir (lo cual borra lo que había), "a" para agregar al final. Una vez perdí un archivo por usar "w" cuando en realidad quería "a", así que piensa bien el modo antes de escribirlo. La instrucción with
with open(...) administra el archivo por ti, cerrándolo automáticamente cuando termina el bloque con sangría, incluso si ocurre un error. Usa siempre with open(...) en lugar de open()/close() manuales. 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 de administrador de contexto de Python: ejecuta código de preparación y de cierre por ti, en este caso 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 con sangría, y luego lo cierra por ti cuando el bloque termina, incluso si algo falla en el camino. Úsalo siempre en lugar de llamar a .close() tú mismo, es una cosa menos de la que preocuparte. Leer archivos
Hay tres métodos para leer. .read() carga el archivo completo como una sola cadena. .readline() lee una línea. Iterar directamente sobre el objeto de archivo lee línea por línea, lo cual es el enfoque 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() # el archivo entero 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 sola 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 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 sola cadena, .readline() te da una sola línea. Cuando el archivo puede ser grande, usa un bucle con for line in f, ya que lee una línea a la vez y así nunca cargas todo en memoria. El .strip() en cada línea elimina el salto de línea final. Escribir archivos
El modo "w" sobrescribe el archivo por completo si ya existe. El modo "a" agrega 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 de una 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 ya existe. "a" agrega al final.
f.write() no agrega un salto de línea automáticamente, así que incluye "\n" explícitamente. Para escribir varias líneas de una sola 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" agrega al final sin borrarlo. .write() no agrega un salto de línea por ti, así que añade "\n" a cada línea tú mismo. Para un lote de líneas, "\n".join(lines) las une primero. Excepciones
Cuando Python encuentra 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 y muestra un traceback. La siguiente tabla muestra las excepciones más comunes que encontrarás.
Excepciones comunes que encontrarás:
| Excepción | Cuándo ocurre |
|---|---|
FileNotFoundError | open() no puede encontrar el archivo |
ValueError | Una función recibe un valor del tipo correcto pero con contenido incorrecto, por ejemplo int("abc") |
TypeError | Tipo completamente incorrecto, por ejemplo "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 |
FileNotFoundError, ValueError, KeyError, TypeError. Sin manejar, detienen el programa y muestran un traceback, que parece alarmante pero en realidad es un mapa que señala directamente dónde se rompió. try / except
Envuelve en un bloque try el código que podría fallar. Si ocurre una excepción, el bloque except correspondiente la maneja en lugar de dejar que el programa falle. Sé específico sobre qué excepción capturas: capturar todo con un except: desnudo 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 capturas. Capturar todas las excepciones con un except: desnudo esconde errores:
# mal, captura todo, incluyendo errores del programador
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")try el código que podría fallar, y el except correspondiente se ejecuta en lugar de que el programa falle. Nombra la excepción que esperas, como ValueError, en lugar de un except: desnudo, que captura todo y te esconde errores reales. Captura lo que realmente puedas manejar, y deja que el resto se muestre. Capturar múltiples excepciones
Puedes manejar distintos 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 captura varios 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 para que puedas inspeccionar el mensaje.
except, y Python ejecuta el primero que coincida. Para manejar varios de la misma manera, lístalos en una tupla: except (ValueError, ZeroDivisionError). La parte as e te entrega el objeto del error para que puedas imprimir su 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é pase.
try:
with open("data.txt") as f:
content = f.read()
except FileNotFoundError:
print("Archivo no encontrado, usando valores predeterminados")
content = ""
else:
print("Archivo cargado correctamente")
finally:
print("Intento de carga del archivo finalizado") # siempre se ejecutafinally es más útil para tareas de limpieza (cerrar conexiones, liberar bloqueos) incluso cuando ya estás usando with para archivos.
else se ejecuta solo cuando el try no lanzó nada, un lugar prolijo para el resto del camino exitoso. finally se ejecuta siempre, haya o no excepción, lo que lo convierte en el lugar para la limpieza que tiene que pasar sin importar el resultado. Con archivos, with ya cubre la limpieza, así que recurrirás a finally menos de lo que imaginas. raise
Puedes lanzar excepciones tú mismo con raise. Así es como haces que tus funciones señalen problemas con claridad a quien las llama, en lugar de devolver silenciosamente 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 los problemas con claridad a quien las llama.
raise ExceptionType("mensaje") permite que tu propio código señale un problema en voz alta en lugar de seguir con un valor incorrecto. Es la manera en que una función dice "no puedo trabajar con esto" para que quien la llama se encargue. Claro y temprano es mejor que una respuesta incorrecta que aparece mucho más adelante. 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 llamen a tu código 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"No se puede retirar {amount}, el saldo es {self.balance}"
)
self.balance -= amounttry:
account.withdraw(1000)
except InsufficientFundsError as e:
print(f"Transacción rechazada: {e}")Exception, y un cuerpo de clase vacío basta para empezar. Le da a quien llama un nombre propio que capturar, separado de los errores incorporados, así que los problemas de tu programa se distinguen por sí solos. JSON
JSON es el formato que todos hablan: APIs, archivos de configuración, exportaciones de datos. El módulo json de Python lo maneja directamente. json.load() lee JSON desde un archivo y lo convierte en 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) # interpreta el JSON como un dict/list de Python
print(config["setting"])Escribir JSON en un archivo:
import json
data = {"name": "Ana", "score": 87, "active": True}
with open("output.json", "w") as f:
json.dump(data, f, indent=2) # indent= lo hace legible para humanosCorrespondencia entre tipos de JSON y de 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 de Python sin tocar un archivo:
import json
# de cadena a Python
data = json.loads('{"name": "Ana", "score": 87}')
# de Python a cadena
text = json.dumps({"name": "Ana", "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 el JSON de un archivo en diccionarios y listas de Python, json.dump(data, f) los escribe de vuelta. Las versiones con s, json.loads y json.dumps, hacen lo mismo con una cadena en lugar de un archivo. Agrega indent=2 cuando quieras que la salida sea legible. En la práctica
Un patrón de guardar/cargar para un juego pequeño: escribe el estado en JSON, cárgalo de vuelta en la siguiente ejecución, y usa valores predeterminados 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": "Jugador", "score": 0, "level": 1}
state = load_game()
state["score"] += 50
save_game(state)
