Skip to content
This page has been auto-translated and may contain errors.View in English

Archivos y excepciones

docs.scrimba.com

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.

E/S de archivos y manejo de excepciones son los dos mecanismos que hacen un programa confiable. open() da acceso al sistema de archivos; with se asegura de que el archivo se cierre incluso en caso de error. try/except atrapa tipos de excepciones específicas para que puedas recuperarte en lugar de colapsar. Juntos son la base de cualquier script que se ejecuta sin vigilancia.

Los archivos y excepciones son donde un script se encuentra con el mundo externo desordenado: una ruta que no existe, un disco que se llena, entrada que no se analiza. open() devuelve un objeto de archivo, y with garantiza que se cierre incluso cuando el código dentro levanta una excepción. Las excepciones son cómo Python señala el fracaso: cuando una se levanta, viaja hacia arriba a través de las llamadas que la llevaron allí, y el primer except coincidente en el camino la maneja. La disciplina de este capítulo es atrapar los fracasos específicos de los que realmente puedes recuperarte y dejar que el resto salga a la luz ruidosamente, en lugar de tragarlo todo y enviar un script que falla en silencio.

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.

open(path, mode) devuelve un objeto de archivo. La cadena de modo controla el acceso: "r" lee, "w" escribe (borrando el archivo primero), "a" añade al final. Añade "b" para modo binario cuando el archivo no es texto. Una cosa que vale la pena establecer desde el primer día: pasa encoding="utf-8" para archivos de texto. Déjalo fuera y Python usa lo que sea el predeterminado de la máquina, así que un archivo que se lee bien en tu laptop puede volver con caracteres rotos en otra persona.

open() le pide al SO un identificador en el archivo y devuelve un objeto de archivo envuelto alrededor de él. El modo elige tanto el acceso ("r", "w", "a") como si obtienes texto o bytes sin procesar ("b"). El parámetro que muerde a la gente es encoding: decide cómo los bytes en disco se asignan a caracteres. Déjalo sin establecer y Python vuelve al predeterminado de la localidad de la máquina, que difiere entre plataformas, así que un archivo escrito en una máquina puede decodificarse mal en otra. Siempre pasa encoding="utf-8" para texto a menos que tengas una razón específica para no hacerlo, y las sorpresas desaparecen principalmente.

python
f = open("data.txt", "r")    # "r" = leer
content = f.read()
f.close()

El "r" es el modo:

ModoSignificado
"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.

JunoAbriendo archivosopen(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.
JunoAbriendo archivosopen(path, mode) devuelve un objeto de archivo: "r" leer, "w" sobrescribir, "a" añadir, más "b" para binario. Pasa encoding="utf-8" en archivos de texto para que se lean igual en cada máquina, y deja que with maneje el cierre.
JunoAbriendo archivosopen() te da un objeto de archivo sobre un identificador del SO; el modo establece acceso y texto-versus-bytes. La configuración que viaja mal es encoding: déjalo fuera y heredas el predeterminado de la localidad de la máquina, así que haz que encoding="utf-8" sea el hábito para texto. Nunca dependas de `.close()` a mano cuando with lo hará por ti.

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 es la sintaxis context manager de Python: un pequeño protocolo donde un objeto obtiene una llamada "configurar" cuando el bloque comienza y una llamada "desmontar" cuando termina. Para un archivo, la configuración devuelve el archivo abierto y el desmontaje lo cierra. La ganancia es que el desmontaje se ejecuta incluso si se levanta una excepción dentro del bloque, así que obtienes la limpieza de un try/finally sin escribir uno alrededor de cada archivo.

Un context manager (cualquier objeto que puedas poner después de with) define dos piezas de comportamiento: qué hacer en la entrada, y qué hacer en la salida. with open(...) as f ejecuta el paso de entrada, vincula el archivo a f, y registra el paso de salida, que Python ejecuta cuando el bloque termina, ya sea que terminó normalmente o porque se levantó una excepción. Para archivos el paso de salida es close(). Dos notas prácticas. Puedes abrir varios archivos en una declaración, with open(a) as f, open(b) as g:, y ambos cierran en orden. Y el paso de salida puede elegir tragar la excepción, que es cómo ayudantes como contextlib.suppress(FileNotFoundError) funcionan, así que sabe que un bloque with puede absorber silenciosamente un error si el context manager fue escrito para hacerlo.

python
with open("data.txt", "r") as f:
    content = f.read()

# f está cerrado aquí, garantizado

with 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().

JunoLa declaración withwith 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.
JunoLa declaración withwith ejecuta la configuración cuando el bloque se abre y la limpieza cuando se cierra, y la limpieza se dispara incluso en una excepción. Para un archivo esa limpieza es close(), así que obtienes seguridad try/finally sin escribir una. Prefierelo sobre `open()`/`close()` manual cada vez.
JunoLa declaración with Un context manager cierra el archivo en la salida ya sea que el bloque terminó o levantó una excepción, así que with reemplaza try/finally escrito a mano para limpieza. Abre varios a la vez con un solo with. Ten en mente que el paso de salida puede suprimir una excepción, que es exactamente para qué sirve `contextlib.suppress` y vale la pena reconocer cuando un bloque `with` traga un error.

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.

.read() carga el archivo completo en memoria como una cadena. .readline() lee una sola línea, salto de línea incluido. .readlines() devuelve una lista de cada línea. Para cualquier cosa que podría ser grande, itera el objeto de archivo directamente con for line in f: extrae una línea a la vez y nunca mantiene el archivo completo a la vez, lo que mantiene la memoria plana sin importar cuán grande sea el archivo.

.read() devuelve el archivo completo como una cadena, así que su costo de memoria se escala con el tamaño del archivo: bien para una configuración, un problema para un registro de varios gigabytes. Iterar el objeto de archivo, for line in f, lee una línea por paso y la descarta antes de la siguiente, así que la memoria permanece plana independientemente de la longitud. Ese es el patrón por el que es bueno ir por defecto para archivos de tamaño desconocido. .readlines() parece conveniente pero construye una lista de cada línea a la vez, que tiene el mismo tamaño de huella de memoria que .read(), así que no te compra nada sobre el bucle. La regla que previene el fallo clásico de falta de memoria: busca el bucle for line in f primero, y solo llama a .read() cuando sabes que el archivo es pequeño.

python
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:

python
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 final

Iterar directamente sobre el objeto de archivo (for line in f) es la forma más eficiente de leer un archivo grande.

JunoLeyendo archivos.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.
JunoLeyendo archivos.read() carga todo a la vez, que es bien para archivos pequeños y riesgoso para los grandes. Ve por defecto a for line in f: transmite una línea a la vez y mantiene la memoria plana sea cual sea el tamaño. .readlines() se ve ordenado pero usa tanta memoria como `.read()`.
JunoLeyendo archivos.read() y .readlines() ambos sostienen el archivo completo en memoria, así que escalan con el tamaño del archivo. El bucle `for line in f` lee y descarta una línea a la vez, manteniendo la memoria plana, que es lo que detiene la sorpresa de falta de memoria en un archivo que no dimensionaste primero. Busca el bucle por defecto, `.read()` solo cuando sabes que es pequeño.

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().

.write(s) escribe una cadena y, a diferencia de print(), no agrega nada, sin espacio y sin salto de línea, así que pones el "\n" tú mismo. .writelines(lines) escribe cada cadena en una lista una detrás de otra, de nuevo sin separadores agregados, así que los saltos de línea ya tienen que estar en tus cadenas. Observa el modo: "w" borra el archivo el instante en que lo abres, antes de que hayas escrito algo, así que abrir con "w" cuando quisiste "a" descarta los contenidos antiguos.

Dos cosas sobre escribir que causan errores reales. Primero, los datos que escribes no necesariamente golpean el disco de inmediato: el SO los sostiene en un búfer y escribe en lotes, y es .close() (que with llama por ti) lo que se asegura de que todo está en el disco. Si un proceso de larga duración escribe y nunca cierra, la cola de la salida puede sentarse en el búfer y perderse en un colapso, así que cierra el archivo o llama a .flush() en los puntos donde los datos tienen que ser seguros. Segundo, el modo decide el daño: "w" vacía el archivo el momento en que se abre, antes de cualquier escritura, así que alcanzar "w" cuando querías "a" destruye los contenidos anteriores sin advertencia. .writelines(lines) no agrega separadores de su propia cuenta, así que los saltos de línea ya tienen que estar en las cadenas.

python
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:

python
lines = ["Línea uno", "Línea dos", "Línea tres"]

with open("output.txt", "w") as f:
    f.write("\n".join(lines) + "\n")
JunoEscribiendo archivos"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.
JunoEscribiendo archivos"w" borra el archivo el momento en que se abre, "a" añade, así que elegir el incorrecto silenciosamente lanza los contenidos antiguos. .write() no agrega nada, sin salto de línea, así que proporcionas `"\n"` tú mismo. .writelines() escribe una lista sin separadores tampoco, los saltos de línea ya tienen que estar en las cadenas.
JunoEscribiendo archivos"w" vacía el archivo el instante en que se abre, así que alcanzar `"w"` en lugar de `"a"` destruye los contenidos anteriores sin advertencia. Las escrituras se sientan en un búfer hasta que `.close()` (que `with` llama) las descarga, así que un proceso que escribe y nunca cierra puede perder su cola en un colapso. .write() y .writelines() no agregan saltos de línea, eso es cosa tuya.

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.

Una excepción es un objeto, y las excepciones forman un árbol familiar: las específicas como FileNotFoundError son un tipo de una más amplia (OSError), y casi todo lo que atrapas es un tipo de Exception. Ese árbol es lo que hace que atrapar funcione: atrapar un tipo padre también atrapa sus hijos. Cuando una se levanta, Python abandona la línea actual y camina hacia atrás a través de las llamadas que la llevaron allí, buscando una except coincidente. Si no encuentra ninguna, el programa se detiene e imprime un traceback: la lista de llamadas de donde sucedió el error hasta la parte superior.

Las excepciones forman una jerarquía de clases (un árbol familiar de tipos), y ese árbol es todo el juego cuando las atrapas: una except para un tipo padre también atrapa cada tipo abajo de él, así que except Exception es una red ancha y except FileNotFoundError es una estrecha. La única pieza del árbol que vale la pena memorizar es que KeyboardInterrupt (el usuario presionando Ctrl-C) y SystemExit (una solicitud de apagado limpio) se sientan al lado de Exception, no debajo de ella. Eso es deliberado: significa que una except Exception amplia deja pasar esos dos, así que un Ctrl-C aún detiene tu programa incluso dentro de una captura general. Esto es exactamente por qué alcanzas except Exception y nunca una except: simple, que atrapa esos dos también y atrapa al usuario dentro de un programa que intenta abandonar.

Las excepciones comunes que encontrarás:

ExcepciónCuándo sucede
FileNotFoundErroropen() no puede encontrar el archivo
ValueErrorLa función obtiene un valor del tipo correcto pero contenido incorrecto, p. ej. int("abc")
TypeErrorTipo completamente incorrecto, p. ej. "hola" + 5
KeyErrorLa clave del diccionario no existe
IndexErrorÍndice de lista fuera de rango
ZeroDivisionErrorDivisión por cero
AttributeErrorEl objeto no tiene ese atributo o método
JunoExcepciones Una excepción es Python diciéndote que algo salió mal. Encontrarás algunos constantemente: `FileNotFoundError`, `ValueError`, `KeyError`, `TypeError`. Si no se manejan detienen el programa e imprimen un traceback, que se ve alarmante pero es realmente un mapa que apunta directamente a dónde se rompió.
JunoExcepciones Las excepciones son objetos arreglados en un árbol familiar, y atrapar un tipo padre también atrapa sus hijos. Cuando una se levanta Python camina hacia atrás las llamadas buscando una `except` coincidente, e imprime un traceback si no encuentra ninguna. Conocer los nombres comunes, `ValueError`, `KeyError`, `FileNotFoundError`, te permite atrapar el correcto.
JunoExcepciones La jerarquía de tipos es todo el punto: `except Exception` es la red ancha, un tipo específico es la estrecha. `KeyboardInterrupt` y `SystemExit` se sientan al lado de `Exception`, no debajo de ella, así que un Ctrl-C aún escapa de `except Exception`. Esa es la razón para usarlo sobre una simple `except:`, que atrapa al usuario dentro de un programa que intenta abandonar.

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/except atrapa un tipo de excepción específico y ejecuta el manejador en lugar de colapsar. Nombrar el tipo importa: atrapa aquello de lo que realmente puedes recuperarte, así un error no relacionado sale a la luz ruidosamente en lugar de ser tragado. Vincula la excepción con as e cuando quieras leer su mensaje. Lista varios cláusulas except para diferentes tipos y Python usa el primero que se ajusta.

Una except SomeType coincide cuando la excepción levantada es ese tipo o un subtipo del mismo (una clase más específica debajo en el árbol), que es por qué atrapar un padre atrapa todos sus hijos. Python intenta cada except de arriba hacia abajo y se detiene en el primero que coincide, así que ordénalas específica antes que general: una cláusula amplia colocada primero atrapará todo y las más estrechas abajo nunca se ejecutan. La disciplina que mantiene try/except de ocultar errores: atrapa el tipo más estrecho que puedas manejar, mantén el cuerpo try hacia la sola línea que realmente puede levantarse, y deja que todo lo demás se propague. Un try envuelto alrededor de veinte líneas convierte un fracaso esperado en una máscara sobre diecinueve inesperados.

python
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:

python
# 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")
Junotry / except Pon código que podría fallar en un bloque 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.
Junotry / excepttry/except atrapa un tipo nombrado y se recupera en lugar de colapsar; as e te da el mensaje. Atrapa el tipo específico que puedas manejar así los errores no relacionados aún salen a la luz. Mantén el cuerpo `try` pequeño, envolver veinte líneas convierte un fracaso esperado en una máscara sobre el resto.
Junotry / except Una except coincide con su tipo y cualquier subtipo, y Python toma la primer cláusula que se ajusta, así que ordena específica antes que general o la amplia sombrea el resto. Atrapa el tipo más estrecho, mantén el `try` hacia la línea que realmente puede levantarse, y deja que todo lo demás se propague. Un `try` amplio oculta mucho más que el fracaso que quisiste manejar.

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.

Apila varios cláusulas except para diferentes tipos y Python las chequea de arriba hacia abajo, tomando la primer que se ajusta. Para manejar varios tipos de la misma manera, agrúpalos en una tupla: except (ValueError, ZeroDivisionError). as e vincula la excepción así puedas leer su mensaje. El orden importa: pon los tipos específicos antes que los generales, o una cláusula amplia más arriba atrapa todo y los de abajo nunca se ejecutan.

El orden es la trampa. Python toma el primer except cuyo tipo coincida, y un tipo amplio coincide con sus parientes más estrechos, así que listar except Exception arriba de except ValueError significa que la cláusula ValueError es código muerto que nunca se ejecuta. Específico primero, general al último. Agrupa tipos que comparten una respuesta con except (A, B) as e. Cuando atrapas un error solo para añadir contexto y pasarlo, levanta el nuevo con from: raise ValueError("bad config") from e. Eso mantiene el error original visible debajo del nuevo en el traceback, así el reporte muestra tanto el significado de alto nivel como la causa de bajo nivel en lugar de reemplazar uno con el otro.

python
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:

python
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.

JunoAtrapando múltiples excepciones Dale a cada tipo de error su propio bloque 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.
JunoAtrapando múltiples excepciones Múltiples cláusulas except se ejecutan de arriba hacia abajo, primer coincidencia gana, así lista tipos específicos antes que generales o la amplia sombrea el resto. Una tupla, except (A, B), maneja varios con un bloque. as e vincula la excepción así puedas leer su mensaje.
JunoAtrapando múltiples excepciones Las cláusulas específicas van primero: un tipo amplio arriba de uno estrecho hace que la cláusula estrecha sea código muerto. Agrupa respuestas compartidas con except (A, B) as e. Cuando re-levantas para añadir contexto, usa raise NuevoError("...") from e así la causa original permanece visible debajo del nuevo error en el traceback en lugar de desaparecer.

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é.

else sostiene el código que debería ejecutarse solo cuando el cuerpo try no levantó nada. Mantenerlo fuera del try aclara la intención: no estás atrapando excepciones del camino del éxito accidentalmente. finally es la garantía de limpieza, se ejecuta sin importar qué, ya sea que el bloque tuvo éxito, levantó, fue atrapado, o incluso golpeó un return. Eso es lo que lo hace el hogar correcto para soltar un recurso que tiene que dejarse ir independientemente del resultado.

else se ejecuta solo cuando el cuerpo try terminó limpiamente, lo que mantiene el código del camino del éxito fuera del try así no puede disparar accidentalmente una de tus cláusulas except. finally se ejecuta incondicionalmente: después del éxito, después de una excepción atrapada, después de una no atrapada en su camino hacia arriba, incluso después de un return en el try. Ese último caso esconde una trampa que vale la pena saber: un return dentro de finally anula un return del try y, peor, silenciosamente traga una excepción que estaba propagándose, así el error desaparece sin traza. Mantén finally a limpieza, nunca return desde él. Para archivos, with ya te da esta garantía, así alcanza finally principalmente cuando estés sosteniendo un recurso que no tiene context manager.

python
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 ejecuta

finally es más útil para limpieza (cerrar conexiones, liberar cerrojos) incluso cuando ya estés usando with para archivos.

Junoelse y finallyelse 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.
Junoelse y finallyelse se ejecuta solo en el camino limpio, manteniendo código del éxito fuera del `try` así no puede disparar tu propio `except`. finally se ejecuta incondicionalmente, incluso pasado un `return`, así es tu garantía de limpieza. Para archivos `with` ya hace esto, así guarda `finally` para recursos sin context manager.
Junoelse y finallyelse es el código del camino limpio que permanece fuera del `try` así no puede disparar tus cláusulas `except`. finally se ejecuta sin importar qué, pero nunca `return` desde él: ese override traga una excepción propagándose y el error desaparece silenciosamente. Mantén `finally` a limpieza, y mayormente solo cuando no hay `with` en la que apoyarse.

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.

raise ExceptionType("message") lanza una excepción desde tu propio código, que es cómo una función reporta una entrada mala en lugar de cojear con un valor incorrecto. Dentro de un bloque except, una raise simple (sin argumento) re-lanza la excepción que atrapaste, útil cuando quieres registrarla o reaccionar y aún dejarla viajar hacia arriba. Levantar deliberadamente hace que los modos de fracaso de una función sean parte de su contrato, así los llamadores pueden atraparlo y manejarlo por nombre.

raise SomeError("message") reporta un fracaso a propósito, el movimiento correcto cuando una función obtiene entrada que no puede honrar: rechazar ruidosamente vence a devolver una respuesta incorrecta que aparece como un misterio después. Dentro de un except, una raise simple re-lanza la excepción que atrapaste con su traceback original intacto, así puedas registrar y re-lanzar sin borrar de dónde vino. Cuando quieres traducir un error de bajo nivel a uno de dominio, usa from: raise ConfigError("bad settings") from e mantiene el original debajo del nuevo en el reporte, así un lector ve tanto el significado como la causa en lugar de reemplazar uno con el otro. La regla de oro: re-lanza para añadir contexto, nunca para ocultarlo.

python
def divide(a, b):
    if b == 0:
        raise ValueError("No se puede dividir por cero")
    return a / b

Esto hace que tus funciones sean explícitas sobre lo que esperan y señala problemas claramente a los llamadores.

Junoraiseraise 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.
Junoraiseraise ExceptionType("message") reporta un fracaso desde tu código, haciendo que los casos de error de una función sean parte de cómo los llamadores la usan. Una simple `raise` dentro de un `except` re-lanza lo que atrapaste, manteniendo el traceback original, así puedas registrar y aún dejarla viajar hacia arriba.
Junoraise Levanta deliberadamente cuando la entrada no puede honrarse: rechazar ruidosamente vence a una respuesta incorrecta que aparece después como un misterio. Una simple `raise` re-lanza con el traceback original intacto. Para traducir un error de bajo nivel a uno de dominio, usa raise ErrorDedominio("...") from e así la causa permanece visible, re-lanza para añadir contexto, nunca para enterrarlo.

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.

Una excepción personalizada es tu propia clase que hereda de Exception (se convierte en un nuevo tipo de excepción, así que encaja en el mismo árbol familiar). La recompensa es un nombre que los llamadores pueden atrapar precisamente, separado de errores incorporados. Para un grupo de fracasos relacionados, haz una clase base y subclasifícala para cada caso específico: los llamadores atrapan la base para manejar el grupo completo, o una subclase para aislar uno.

Subclasifica Exception, no BaseException (la raíz que también cubre Ctrl-C y apagado, que no quieres que tu error esté agrupado con). La razón para definir tu propio tipo en lugar de reutilizar ValueError es el árbol familiar: construye una base como PaymentError y subclasifícala para cada modo (InsufficientFundsError, CardDeclinedError), y un llamador puede atrapar PaymentError para manejar la categoría completa o la subclase para un caso, sin analizar cadenas de mensaje. Dale a la clase un __init__ que lleve los campos relevantes (la cuenta, el monto) así el manejador puede inspeccionar qué sucedió en código en lugar de raspar el texto, que hace el error algo que un programa puede actuar, no solo imprimir.

python
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 -= amount
python
try:
    account.withdraw(1000)
except SaldoInsuficienteError as e:
    print(f"Transacción rechazada: {e}")
JunoClases de excepciones personalizadas Haz tu propio tipo de error heredando de 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.
JunoClases de excepciones personalizadas Una excepción personalizada es una clase heredando de Exception, dándole a los llamadores un nombre preciso para atrapar. Para fracasos relacionados, haz una clase base y subclasifícala por caso: atrapa la base para el grupo completo, una subclase para uno. Puedes añadir campos para llevar detalles más allá de la cadena de mensaje.
JunoClases de excepciones personalizadas Subclasifica Exception, no BaseException, así tu error no está agrupado con Ctrl-C. La razón para definir el tuyo es el árbol: una base PaymentError con subclases específicas permite a un llamador manejar la categoría o un caso sin raspar texto de mensaje. Pon los campos relevantes en `__init__` así los manejadores actúan sobre datos, no cadenas.

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.

Los pares se dividen por lo que tocan. json.load() / json.dump() trabajan con un objeto de archivo, json.loads() / json.dumps() (los con la s) trabajan con una cadena en memoria. Pasa indent=2 a dump/dumps cuando un humano leerá la salida, de lo contrario viene en una línea. JSON inválido levanta json.JSONDecodeError, que es un tipo de ValueError, así un manejador que atrapa ValueError ya la cubre.

Los cuatro nombres son dos operaciones cruzadas con dos fuentes: load/dump para archivos, loads/dumps para cadenas (la s es "string"). El detalle que atrapa a la gente en producción es lo que JSON no puede representar. No tiene noción de una fecha, un set, o un Decimal, así json.dumps(datetime.now()) levanta TypeError. Lo manejas pasando default=, una función que dump/dumps llama en cualquier valor que no conoce, que debería devolver algo que JSON pueda sostener, usualmente str(value). La otra regla que vale la pena sostener: los números hacen viaje de vuelta como float, así un valor que escribiste como Decimal por exactitud viene de vuelta como un float y lo pierde. Nunca almacenes dinero o cualquier cosa que necesite decimales exactos (ver el capítulo Números) como un número JSON simple.

Leer JSON desde un archivo:

python
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:

python
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 humanos

Mapeo de tipo JSON a Python:

JSONPython
objeto {}dict
arreglo []list
cadena ""str
númeroint o float
true / falseTrue / False
nullNone

Para convertir entre cadenas JSON y objetos Python sin tocar un archivo:

python
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.

JunoJSONjson.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.
JunoJSONload/dump manejan archivos, loads/dumps manejan cadenas, la `s` es para cadena. indent=2 lo hace legible para humanos. JSON malo levanta json.JSONDecodeError, que es un `ValueError`, así atrapar `ValueError` ya cubre una falla de análisis.
JunoJSON Dos operaciones cruzadas con dos fuentes: load/dump para archivos, loads/dumps para cadenas. JSON no tiene fecha, set, o `Decimal`, así volcar esos levanta `TypeError` a menos que pases `default=`. Y los números hacen viaje de vuelta como `float`, así nunca almacenes dinero como un número JSON simple, pierde su exactitud en el camino de vuelta.

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:

python
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)

Cargando un archivo de configuración y guardando resultados, con manejo de excepciones específicas para cada modo de fracaso:

python
import json

def load_config(path: str) -> dict:
    try:
        with open(path, "r") as f:
            return json.load(f)
    except FileNotFoundError:
        raise FileNotFoundError(f"Archivo de configuración no encontrado: {path}")
    except json.JSONDecodeError as e:
        raise ValueError(f"JSON inválido en {path}: {e}")

def save_results(results: list[dict], path: str) -> None:
    with open(path, "w") as f:
        json.dump(results, f, indent=2)
    print(f"Guardados {len(results)} resultado(s) a {path}")

config = load_config("experiment.json")
results = [{"epoch": 1, "loss": 0.82}, {"epoch": 2, "loss": 0.61}]
save_results(results, "results.json")

Un escritor de registro estructurado que añade entradas con marca de tiempo a un archivo, con un manejador de nivel superior que atrapa y registra fallos inesperados:

python
import json
from datetime import datetime

ARCHIVO_REGISTRO = "run.log"

def log(level: str, message: str) -> None:
    ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    entry = f"[{ts}] [{level.upper():7}] {message}\n"
    with open(ARCHIVO_REGISTRO, "a") as f:
        f.write(entry)

def process(config_path: str) -> None:
    log("info", f"Iniciando trabajo, configuración: {config_path}")
    try:
        with open(config_path) as f:
            config = json.load(f)
        log("info", f"Configuración cargada: {config}")
    except FileNotFoundError:
        log("error", f"Configuración no encontrada: {config_path}")
        raise
    except json.JSONDecodeError as e:
        log("error", f"JSON malo en configuración: {e}")
        raise

try:
    process("config.json")
except Exception as e:
    log("critical", f"Trabajo falló: {e}")

Re-levantar después de registrar preserva el traceback original para el llamador. El except Exception de nivel superior atrapa cualquier cosa que se deslizó a través, la registra como crítica, y deja que el proceso salga limpiamente.