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

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.

El manejo de archivos y de excepciones son los dos mecanismos que hacen confiable a un programa. open() te da acceso al sistema de archivos; with asegura que el archivo se cierre correctamente incluso si ocurre un error. try/except captura tipos de excepción específicos para que puedas recuperarte en lugar de fallar. Juntos son la base de cualquier script que se ejecuta sin supervisión.

Los archivos y las excepciones son el punto donde un script se topa con el mundo exterior, que es desordenado: una ruta que no existe, un disco que se llena, una entrada que no se puede interpretar. open() te devuelve un objeto de archivo, y with garantiza que se cierre incluso cuando el código dentro del bloque lanza un error. Las excepciones son la manera en que Python señala esa falla: cuando se lanza una, viaja hacia arriba a través de las llamadas que la originaron, y el primer except que coincida en ese recorrido la maneja. La disciplina de la que trata este capítulo es capturar específicamente las fallas de las que realmente puedes recuperarte y dejar que el resto se manifieste con claridad, en lugar de tragarte todo y terminar entregando un script que falla en silencio.

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.

open(path, mode) devuelve un objeto de archivo. La cadena de modo controla el acceso: "r" lee, "w" escribe (borrando primero el archivo), "a" agrega al final. Añade "b" para modo binario cuando el archivo no sea texto. Algo que conviene establecer desde el primer día: pasa encoding="utf-8" para archivos de texto. Si lo dejas sin especificar, Python usa el valor predeterminado de la máquina, así que un archivo que se lee bien en tu laptop puede volver con caracteres corruptos en la máquina de otra persona.

open() le pide al sistema operativo un identificador (handle) sobre el archivo y devuelve un objeto de archivo que lo envuelve. El modo determina tanto el acceso ("r", "w", "a") como si obtienes texto o bytes crudos ("b"). El parámetro que suele morder a la gente es encoding: decide cómo se traducen los bytes en disco a caracteres. Si lo dejas sin definir, Python recurre al valor predeterminado del entorno local (locale) de la máquina, que varía entre plataformas, así que un archivo escrito en una máquina puede decodificarse mal en otra. Pasa siempre encoding="utf-8" para texto salvo que tengas una razón específica para no hacerlo, y la mayoría de las sorpresas desaparecen.

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

El "r" es el modo:

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

JunoAbrir archivosopen(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.
JunoAbrir archivosopen(path, mode) devuelve un objeto de archivo: "r" lee, "w" sobrescribe, "a" agrega, más "b" para binario. Pasa encoding="utf-8" en archivos de texto para que se lean igual en cualquier máquina, y deja que with se encargue de cerrarlo.
JunoAbrir archivosopen() te da un objeto de archivo sobre un identificador del sistema operativo; el modo define el acceso y si es texto o bytes. La configuración que causa más problemas es encoding: si la omites, heredas el valor predeterminado del entorno de la máquina, así que convierte encoding="utf-8" en un hábito para texto. Nunca dependas de llamar .close() a mano cuando with puede hacerlo por ti.

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 es la sintaxis de administrador de contexto de Python: un pequeño protocolo donde un objeto recibe una llamada de "preparación" cuando el bloque comienza y una de "cierre" cuando termina. Para un archivo, la preparación devuelve el archivo abierto y el cierre lo cierra. La ventaja es que el cierre se ejecuta incluso si se lanza una excepción dentro del bloque, así que obtienes la limpieza de un try/finally sin tener que escribir uno alrededor de cada archivo.

Un administrador de contexto (cualquier objeto que puedas colocar después de with) define dos comportamientos: qué hacer al entrar y qué hacer al salir. 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, sin importar si terminó de forma normal o porque se lanzó una excepción. Para archivos, el paso de salida es close(). Dos notas prácticas. Puedes abrir varios archivos en una sola instrucción, with open(a) as f, open(b) as g:, y ambos se cierran en orden. Y el paso de salida puede optar por absorber la excepción, que es como funcionan ayudantes como contextlib.suppress(FileNotFoundError), así que ten en cuenta que un bloque with puede absorber un error silenciosamente si el administrador de contexto se escribió para eso.

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

# f está cerrado aquí, garantizado

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

JunoLa instrucción withwith 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.
JunoLa instrucción withwith ejecuta la preparación cuando el bloque abre y la limpieza cuando cierra, y esa limpieza se ejecuta incluso ante una excepción. Para un archivo esa limpieza es close(), así que obtienes la seguridad de try/finally sin escribir uno. Prefiérelo siempre por sobre open()/close() manuales.
JunoLa instrucción with Un administrador de contexto cierra el archivo al salir, ya sea que el bloque haya terminado normalmente o haya lanzado una excepción, así que with reemplaza al try/finally escrito a mano para la limpieza. Abre varios a la vez con un solo with. Ten presente que el paso de salida puede suprimir una excepción, que es exactamente para lo que sirve contextlib.suppress, y vale la pena reconocer cuándo un bloque with está tragándose un error.

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.

.read() carga el archivo completo en memoria como una sola cadena. .readline() lee una sola línea, incluyendo el salto de línea. .readlines() devuelve una lista con todas las líneas. Para cualquier archivo que pueda 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 en memoria, lo que mantiene el uso de memoria constante sin importar cuán grande sea el archivo.

.read() devuelve el archivo completo como una sola cadena, así que su costo de memoria crece con el tamaño del archivo: bien para un archivo de configuración, un problema para un log de varios gigabytes. Iterar el objeto de archivo, for line in f, lee una línea por paso y la descarta antes de pasar a la siguiente, así que la memoria se mantiene constante sin importar la longitud. Ese es el patrón que debes usar por defecto para archivos de tamaño desconocido. .readlines() parece conveniente pero construye una lista con todas las líneas de una sola vez, lo que tiene el mismo costo de memoria que .read(), así que no te aporta nada frente al bucle. La regla que evita el clásico error de falta de memoria: recurre primero al bucle for line in f, y solo llama a .read() cuando sepas que el archivo es pequeño.

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

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 final

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

JunoLeer archivos.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.
JunoLeer archivos.read() carga todo de una sola vez, lo cual está bien para archivos pequeños y es riesgoso para archivos grandes. Usa por defecto for line in f: transmite una línea a la vez y mantiene la memoria constante sin importar el tamaño. .readlines() parece prolijo pero usa tanta memoria como .read().
JunoLeer archivos.read() y .readlines() mantienen el archivo completo en memoria, así que su costo crece con el tamaño del archivo. El bucle for line in f lee y descarta una línea a la vez, manteniendo la memoria constante, y eso es lo que evita la sorpresa de quedarte sin memoria con un archivo cuyo tamaño no verificaste antes. Usa el bucle por defecto, y .read() solo cuando sepas que es pequeño.

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

.write(s) escribe una sola cadena y, a diferencia de print(), no agrega nada, ni espacio ni salto de línea, así que tú pones el "\n" por tu cuenta. .writelines(lines) escribe cada cadena de una lista una tras otra, tampoco agregando separadores, así que los saltos de línea deben estar ya en tus cadenas. Presta atención al modo: "w" borra el archivo en el instante en que lo abres, antes de que hayas escrito algo, así que abrir con "w" cuando querías "a" descarta el contenido anterior.

Hay dos cosas sobre escritura que causan errores reales. Primero, los datos que escribes no necesariamente llegan al disco de inmediato: el sistema operativo los guarda en un búfer y escribe por lotes, y es .close() (que with llama por ti) lo que asegura que todo quede en disco. Si un proceso de larga duración escribe y nunca cierra, la parte final de la salida puede quedarse en el búfer y perderse ante un fallo, así que cierra el archivo o llama a .flush() en los puntos donde los datos deben quedar a salvo. Segundo, el modo determina el daño: "w" vacía el archivo en el momento en que se abre, antes de cualquier escritura, así que usar "w" cuando querías "a" destruye el contenido anterior sin ninguna advertencia. .writelines(lines) no agrega separadores propios, así que los saltos de línea deben estar ya 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 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:

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

with open("output.txt", "w") as f:
    f.write("\n".join(lines) + "\n")
JunoEscribir archivos"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.
JunoEscribir archivos"w" vacía el archivo en el momento en que se abre, "a" agrega, así que elegir el equivocado descarta silenciosamente el contenido anterior. .write() no agrega nada, ni salto de línea, así que tú pones el "\n". .writelines() escribe una lista tampoco con separadores, los saltos de línea deben estar ya en las cadenas.
JunoEscribir archivos"w" vacía el archivo en el instante en que se abre, así que usarlo en lugar de "a" destruye el contenido anterior sin ninguna advertencia. Las escrituras se quedan en un búfer hasta que .close() (que with llama) las vuelca, así que un proceso que escribe y nunca cierra puede perder su parte final ante un fallo. .write() y .writelines() no agregan saltos de línea, eso depende de ti.

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.

Una excepción es un objeto, y las excepciones forman un árbol de familia: las específicas, como FileNotFoundError, son un tipo de una más general (OSError), y casi todo lo que capturas es un tipo de Exception. Ese árbol es lo que hace funcionar la captura: capturar un tipo padre también captura a sus hijos. Cuando se lanza una excepción, Python abandona la línea actual y retrocede a través de las llamadas que la originaron, buscando un except que coincida. Si no encuentra ninguno, el programa se detiene y muestra un traceback: la lista de llamadas desde donde ocurrió el error hasta la parte superior.

Las excepciones forman una jerarquía de clases (un árbol de familia de tipos), y ese árbol es el asunto central cuando las capturas: un except para un tipo padre también captura a todos los tipos debajo de él, así que except Exception es una red amplia y except FileNotFoundError es una red estrecha. La parte del árbol que vale la pena memorizar es que KeyboardInterrupt (el usuario presionando Ctrl-C) y SystemExit (una solicitud de cierre limpio) están junto a Exception, no debajo de él. Eso es deliberado: significa que un except Exception amplio deja pasar esos dos, así que un Ctrl-C sigue deteniendo tu programa incluso dentro de un capturador general. Esta es exactamente la razón por la que se recurre a except Exception y nunca a un except: desnudo, que también captura esos dos y atrapa al usuario dentro de un programa que está intentando cerrar.

Excepciones comunes que encontrarás:

ExcepciónCuándo ocurre
FileNotFoundErroropen() no puede encontrar el archivo
ValueErrorUna función recibe un valor del tipo correcto pero con contenido incorrecto, por ejemplo int("abc")
TypeErrorTipo completamente incorrecto, por ejemplo "hello" + 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 la manera en que Python te dice que algo salió mal. Te encontrarás con algunas constantemente: 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ó.
JunoExcepciones Las excepciones son objetos organizados en un árbol de familia, y capturar un tipo padre también captura a sus hijos. Cuando se lanza una, Python retrocede por las llamadas buscando un except que coincida, y muestra un traceback si no encuentra ninguno. Conocer los nombres comunes, ValueError, KeyError, FileNotFoundError, te permite capturar el correcto.
JunoExcepciones La jerarquía de tipos es todo el asunto: except Exception es la red amplia, un tipo específico es la red estrecha. KeyboardInterrupt y SystemExit están junto a Exception, no debajo de él, así que un Ctrl-C sigue escapando de un except Exception. Esa es la razón para usarlo en lugar de un except: desnudo, que atrapa al usuario dentro de un programa que está intentando cerrar.

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/except captura un tipo de excepción específico y ejecuta el manejador en lugar de fallar. Nombrar el tipo importa: captura aquel del que realmente puedas recuperarte, para que un error no relacionado se manifieste con claridad en lugar de quedar tragado. Vincula la excepción con as e cuando quieras leer su mensaje. Lista varias cláusulas except para distintos tipos, y Python usa la primera que coincida.

Un except SomeType coincide cuando la excepción lanzada es ese tipo o un subtipo de él (un tipo más específico ubicado debajo en el árbol), y por eso capturar un padre también captura a todos sus hijos. Python prueba cada except de arriba hacia abajo y se detiene en la primera coincidencia, así que ordénalos de específico a general: una cláusula amplia colocada primero capturará todo, y las más específicas debajo nunca se ejecutarán. La disciplina que evita que try/except esconda errores: captura el tipo más específico que puedas manejar, reduce el cuerpo del try a la única línea que realmente puede fallar, y deja que todo lo demás se propague. Un try que envuelve veinte líneas convierte una falla esperada en una máscara que cubre otras diecinueve inesperadas.

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

python
# 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")
Junotry / except Pon en un bloque 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.
Junotry / excepttry/except captura un tipo con nombre y se recupera en lugar de fallar; as e te da el mensaje. Captura el tipo específico que puedas manejar para que los errores no relacionados sigan manifestándose. Mantén pequeño el cuerpo del try, envolver veinte líneas convierte una falla esperada en una máscara sobre el resto.
Junotry / except Un except coincide con su tipo y cualquier subtipo, y Python toma la primera cláusula que coincida, así que ordena de específico a general o la amplia opacará al resto. Captura el tipo más específico, reduce el try a la línea que realmente puede fallar, y deja que todo lo demás se propague. Un try amplio esconde mucho más que la falla que querías manejar.

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.

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

El orden es la trampa. Python toma el primer except cuyo tipo coincida, y un tipo amplio coincide con sus parientes más específicos, así que listar except Exception antes de except ValueError hace que la cláusula de ValueError sea código muerto que nunca se ejecuta. Específico primero, general al final. Agrupa tipos que comparten una respuesta con except (A, B) as e. Cuando capturas un error solo para agregarle contexto y propagarlo, lanza el nuevo con from: raise ValueError("bad config") from e. Eso mantiene visible el error original debajo del nuevo en el traceback, así que 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 captura varios 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 para que puedas inspeccionar el mensaje.

JunoCapturar múltiples excepciones Dale a cada tipo de error su propio bloque 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.
JunoCapturar múltiples excepciones Varias cláusulas except se ejecutan de arriba hacia abajo, gana la primera coincidencia, así que lista los tipos específicos antes que los generales o la amplia opacará al resto. Una tupla, except (A, B), maneja varios con un solo bloque. as e vincula la excepción para que puedas leer su mensaje.
JunoCapturar múltiples excepciones Las cláusulas específicas van primero: un tipo amplio arriba de uno estrecho convierte a la cláusula estrecha en código muerto. Agrupa respuestas compartidas con except (A, B) as e. Cuando vuelvas a lanzar para agregar contexto, usa raise NuevoError("...") from e para que la causa original siga 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 habido o no una excepción. finally es útil para tareas de limpieza que deben ocurrir sin importar qué pase.

else contiene el código que debe ejecutarse solo cuando el cuerpo del try no lanzó nada. Mantenerlo fuera del try deja claro la intención: no estás capturando por accidente excepciones del camino exitoso. finally es la garantía de limpieza, se ejecuta sin importar qué pase, ya sea que el bloque haya tenido éxito, haya lanzado un error, haya sido capturado, o incluso haya llegado a un return. Eso es lo que lo convierte en el lugar adecuado para liberar un recurso que tiene que soltarse sin importar el resultado.

else se ejecuta solo cuando el cuerpo del try terminó sin problemas, lo que mantiene el código del camino exitoso fuera del try para que no pueda disparar accidentalmente una de tus cláusulas except. finally se ejecuta sin condiciones: después de un éxito, después de una excepción capturada, después de una no capturada en su camino hacia arriba, incluso después de un return dentro del try. Ese último caso escondte una trampa que vale la pena conocer: un return dentro de finally sobrescribe un return del try y, peor aún, silencia una excepción que se estaba propagando, así que el error desaparece sin dejar rastro. Mantén finally solo para limpieza, nunca hagas return desde ahí. Para archivos, with ya te da esta garantía, así que recurre a finally principalmente cuando estás manejando un recurso que no tiene un administrador de contexto.

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

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

Junoelse y finallyelse 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.
Junoelse y finallyelse se ejecuta solo en el camino limpio, manteniendo el código exitoso fuera del try para que no dispare tu propio except. finally se ejecuta sin condiciones, incluso pasando por un return, así que es tu garantía de limpieza. Para archivos, with ya hace esto, así que reserva finally para recursos sin administrador de contexto.
Junoelse y finallyelse es el código del camino limpio que se mantiene fuera del try para que no dispare tus cláusulas except. finally se ejecuta sin importar qué pase, pero nunca hagas return desde ahí: esa sobrescritura silencia una excepción que se estaba propagando y el error desaparece sin dejar rastro. Mantén finally solo para limpieza, y sobre todo úsalo únicamente cuando no haya un with en el que apoyarte.

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.

raise ExceptionType("mensaje") lanza una excepción desde tu propio código, que es como una función reporta una entrada inválida en lugar de seguir a medias con un valor incorrecto. Dentro de un bloque except, un raise desnudo (sin argumento) vuelve a lanzar la excepción que capturaste, útil cuando quieres registrarla o reaccionar y aun así dejar que siga propagándose. Lanzar deliberadamente hace que los modos de falla de una función formen parte de su contrato, así que quien la llama puede capturarlos y manejarlos por nombre.

raise SomeError("mensaje") reporta una falla a propósito, la decisión correcta cuando una función recibe una entrada que no puede procesar: negarse ruidosamente es mejor que devolver una respuesta incorrecta que se manifiesta como un misterio más adelante. Dentro de un except, un raise desnudo vuelve a lanzar la excepción que capturaste con su traceback original intacto, así que puedes registrarla y volver a lanzarla sin borrar de dónde vino. Cuando quieras 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í que quien lo lea ve tanto el significado como la causa. La regla general: vuelve a lanzar para agregar contexto, nunca para esconderlo.

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 los problemas con claridad a quien las llama.

Junoraiseraise 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.
Junoraiseraise ExceptionType("mensaje") reporta una falla desde tu código, haciendo que los casos de error de una función formen parte de cómo se usa. Un raise desnudo dentro de un except vuelve a lanzar lo que capturaste, conservando el traceback original, así que puedes registrarlo y aun dejar que siga propagándose.
Junoraise Lanza deliberadamente cuando una entrada no puede procesarse: negarse ruidosamente es mejor que una respuesta incorrecta que aparece después como un misterio. Un raise desnudo vuelve a lanzar con el traceback original intacto. Para traducir un error de bajo nivel a uno de dominio, usa raise DomainError("...") from e para que la causa siga visible; vuelve a lanzar para agregar contexto, nunca para enterrarlo.

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.

Una excepción personalizada es tu propia clase que hereda de Exception (se convierte en un nuevo tipo de excepción, así que se integra en el mismo árbol de familia). La ventaja es un nombre que quien llama puede capturar con precisión, separado de los errores incorporados. Para un grupo de fallas relacionadas, crea una clase base y una subclase para cada caso específico: quien llama captura la base para manejar todo el grupo, o una subclase para tratar un caso puntual.

Hereda de Exception, no de BaseException (la raíz que también abarca Ctrl-C y el cierre del programa, con los que no quieres agrupar tu error). La razón para definir tu propio tipo en lugar de reutilizar ValueError es el árbol de familia: construye una base como PaymentError y crea una subclase para cada modo (InsufficientFundsError, CardDeclinedError), y quien llama puede capturar PaymentError para manejar toda la categoría, o la subclase para un caso puntual, sin tener que analizar cadenas de mensaje. Dale a la clase un __init__ que lleve los campos relevantes (la cuenta, el monto) para que el manejador pueda inspeccionar en código lo que ocurrió en lugar de escarbar el texto, lo que convierte al error en algo sobre lo que un programa puede actuar, no solo imprimir.

python
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 -= amount
python
try:
    account.withdraw(1000)
except InsufficientFundsError as e:
    print(f"Transacción rechazada: {e}")
JunoClases de excepción personalizadas Crea tu propio tipo de error heredando de 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.
JunoClases de excepción personalizadas Una excepción personalizada es una clase que hereda de Exception, dándole a quien llama un nombre preciso que capturar. Para fallas relacionadas, crea una clase base y una subclase por caso: captura la base para todo el grupo, una subclase para uno solo. Puedes agregar campos para llevar detalles más allá del mensaje.
JunoClases de excepción personalizadas Hereda de Exception, no de BaseException, para que tu error no quede mezclado con Ctrl-C. La razón para definir el tuyo propio es el árbol: una base PaymentError con subclases específicas permite que quien llama maneje la categoría o un caso puntual sin escarbar el texto del mensaje. Pon los campos relevantes en __init__ para que los manejadores actúen sobre datos, no sobre cadenas.

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.

Los pares se dividen según lo que manejan. json.load() / json.dump() trabajan con un objeto de archivo, json.loads() / json.dumps() (los que tienen la s) trabajan con una cadena en memoria. Pasa indent=2 a dump/dumps cuando un humano vaya a leer la salida, de otro modo aparece todo en una sola línea. Un JSON inválido lanza json.JSONDecodeError, que es un tipo de ValueError, así que un manejador que captura ValueError ya lo cubre.

Los cuatro nombres son dos operaciones cruzadas con dos orígenes: load/dump para archivos, loads/dumps para cadenas (la s es de "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, ni un Decimal, así que json.dumps(datetime.now()) lanza TypeError. Lo manejas pasando default=, una función que dump/dumps llama sobre cualquier valor que no reconozca, y que debe devolver algo que JSON pueda contener, usualmente str(value). La otra regla que vale la pena recordar: los números vuelven como float, así que un valor que escribiste como Decimal por exactitud regresa como float y pierde esa exactitud. Nunca guardes dinero ni nada que requiera decimales exactos (ver el capítulo de 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)    # interpreta el JSON como un dict/list de Python

print(config["setting"])

Escribir JSON en un archivo:

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

Correspondencia entre tipos de JSON y de Python:

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

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

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

JunoJSONjson.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.
JunoJSONload/dump manejan archivos, loads/dumps manejan cadenas, la s es de "string". indent=2 hace la salida legible para humanos. Un JSON incorrecto lanza json.JSONDecodeError, que es un ValueError, así que capturar ValueError ya cubre un fallo de interpretación.
JunoJSON Dos operaciones cruzadas con dos orígenes: load/dump para archivos, loads/dumps para cadenas. JSON no tiene fecha, set ni Decimal, así que volcar esos valores lanza TypeError a menos que pases default=. Y los números vuelven como float, así que nunca guardes dinero como un número JSON simple, pierde su exactitud al regresar.

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:

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

Cargar un archivo de configuración y guardar resultados, con manejo específico de excepciones para cada modo de falla:

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"Se guardaron {len(results)} resultado(s) en {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 log estructurado que agrega entradas con marca de tiempo a un archivo, con un manejador de nivel superior que captura y registra fallas inesperadas:

python
import json
from datetime import datetime

LOG_FILE = "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(LOG_FILE, "a") as f:
        f.write(entry)

def process(config_path: str) -> None:
    log("info", f"Iniciando tarea, config: {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 inválido en la configuración: {e}")
        raise

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

Volver a lanzar después de registrar preserva el traceback original para quien llama. El except Exception de nivel superior captura cualquier cosa que se haya escapado, la registra como crítica, y permite que el proceso termine de forma limpia.