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

Arquivos e exceções ​

A maioria dos programas que fazem algo de fato útil interage com o sistema de arquivos: lendo uma configuração, escrevendo resultados, carregando dados. E quando algo dá errado, o Python levanta uma exceção, um sinal de que algo inesperado aconteceu. Este capítulo cobre os dois lados: como fazer dados entrarem e saírem de arquivos, e como escrever código que trata erros com elegância em vez de simplesmente travar.

E/S de arquivos e tratamento de exceções são os dois mecanismos que tornam um programa confiável. open() dá acesso ao sistema de arquivos; with garante que o arquivo seja liberado mesmo em caso de erro. try/except captura tipos específicos de exceção para que você possa se recuperar em vez de travar. Juntos, eles são a base de qualquer script que roda sem supervisão.

Arquivos e exceções são o ponto em que um script encontra o mundo real e imprevisível: um caminho que não existe, um disco que enche, uma entrada que não pode ser interpretada. open() devolve um objeto de arquivo, e with garante que ele seja fechado mesmo quando o código dentro dele levanta uma exceção. As exceções são a forma como o Python sinaliza essa falha: quando uma é levantada, ela sobe pela cadeia de chamadas que levou até ali, e o primeiro except compatível no caminho a trata. A disciplina que este capítulo ensina é capturar especificamente as falhas das quais você realmente consegue se recuperar e deixar o resto se manifestar em voz alta, em vez de engolir tudo e entregar um script que falha em silêncio.

Abrindo arquivos ​

open() abre um arquivo e retorna um objeto do qual você pode ler ou no qual pode escrever. Você informa o caminho e o que quer fazer com o arquivo (ler, escrever ou acrescentar). Sempre fecha um arquivo quando terminar; a instrução with faz isso automaticamente.

open(path, mode) retorna um objeto de arquivo. A string de modo controla o acesso: "r" lê, "w" escreve (limpando o arquivo antes), "a" acrescenta ao final. Adicione "b" para o modo binário quando o arquivo não for texto. Uma coisa que vale configurar desde o início: passe encoding="utf-8" para arquivos de texto. Se você deixar de fora, o Python usa o padrão da máquina, então um arquivo que abre bem no seu notebook pode voltar com caracteres corrompidos no computador de outra pessoa.

open() pede ao sistema operacional um identificador (handle) para o arquivo e retorna um objeto de arquivo construído em torno dele. O modo define tanto o tipo de acesso ("r", "w", "a") quanto se você recebe texto ou bytes puros ("b"). O parâmetro que costuma pegar as pessoas de surpresa é o encoding: ele decide como os bytes em disco se convertem em caracteres. Se você não definir isso, o Python recorre ao padrão de localidade (locale) da máquina, que varia entre plataformas, então um arquivo escrito em uma máquina pode ser decodificado errado em outra. Sempre passe encoding="utf-8" para texto, a menos que tenha um motivo específico para não fazer isso, e a maioria das surpresas desaparece.

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

O "r" é o modo:

ModoSignificado
"r"Leitura. O arquivo precisa existir. Modo padrão.
"w"Escrita. Cria ou sobrescreve o arquivo.
"a"Acrescentar. Adiciona ao final sem apagar.
"x"Criação. Falha se o arquivo já existir.
"r+"Leitura e escrita.
"b"Binário. Adicione a qualquer modo: "rb", "wb".

Sempre chame .close() quando terminar. Esquecer isso deixa o arquivo aberto e pode fazer você perder dados que ainda estavam esperando para ser gravados. A forma confiável de lidar com isso é a instrução with.

JunoAbrindo arquivosopen(path, mode) te entrega um objeto de arquivo, e o modo diz o que você quer fazer: "r" para ler, "w" para escrever (o que apaga o que já estava lá), "a" para adicionar ao final. Já perdi um arquivo por usar "w" quando queria "a", então visualize o modo antes de digitá-lo.
JunoAbrindo arquivosopen(path, mode) retorna um objeto de arquivo: "r" leitura, "w" sobrescrita, "a" acréscimo, além de "b" para binário. Passe encoding="utf-8" em arquivos de texto para que sejam lidos da mesma forma em qualquer máquina, e deixe o with cuidar do fechamento.
JunoAbrindo arquivosopen() te dá um objeto de arquivo sobre um identificador do sistema operacional; o modo define o acesso e se é texto ou bytes. A configuração que causa dor de cabeça é o encoding: se você não definir, herda o padrão de localidade da máquina, então torne encoding="utf-8" um hábito para arquivos de texto. Nunca dependa de chamar .close() manualmente quando o with pode fazer isso por você.

A instrução with ​

with open(...) gerencia o arquivo para você, fechando-o automaticamente quando o bloco indentado termina, mesmo se ocorrer um erro. Sempre use with open(...) em vez de open()/close() manuais. É mais seguro e é o padrão.

with é a sintaxe de gerenciador de contexto do Python: um pequeno protocolo em que um objeto recebe uma chamada de "preparação" quando o bloco começa e uma chamada de "finalização" quando ele termina. Para um arquivo, a preparação retorna o arquivo aberto e a finalização o fecha. A vantagem é que a finalização é executada mesmo que uma exceção seja levantada dentro do bloco, então você ganha a segurança de um try/finally sem precisar escrever um em volta de cada arquivo.

Um gerenciador de contexto (qualquer objeto que você possa colocar depois de with) define dois comportamentos: o que fazer na entrada e o que fazer na saída. with open(...) as f executa o passo de entrada, vincula o arquivo a f, e registra o passo de saída, que o Python executa quando o bloco termina, seja normalmente ou porque uma exceção foi levantada. Para arquivos, o passo de saída é close(). Duas observações práticas. Você pode abrir vários arquivos em uma única instrução, with open(a) as f, open(b) as g:, e ambos são fechados em ordem. E o passo de saída pode optar por engolir a exceção, que é como funcionam auxiliares como contextlib.suppress(FileNotFoundError), então saiba que um bloco with pode absorver um erro silenciosamente se o gerenciador de contexto tiver sido escrito para isso.

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

# f é fechado aqui, garantidamente

with é a sintaxe de gerenciador de contexto do Python: ele executa código de preparação e finalização para você, aqui abrindo o arquivo e fechando-o de forma confiável. Você não precisa saber como isso funciona internamente para usá-lo com open().

JunoA instrução withwith open(...) as f abre o arquivo, deixa você trabalhar com f dentro do bloco indentado, e depois o fecha para você quando o bloco termina, mesmo se algo der errado no meio do caminho. Use sempre isso em vez de chamar .close() você mesmo, é uma coisa menos para esquecer.
JunoA instrução withwith executa a preparação quando o bloco abre e a limpeza quando ele fecha, e essa limpeza acontece mesmo em caso de exceção. Para um arquivo, essa limpeza é o close(), então você ganha a segurança de um try/finally sem precisar escrever um. Prefira isso em vez de open()/close() manuais sempre.
JunoA instrução with Um gerenciador de contexto fecha o arquivo na saída, tenha o bloco terminado normalmente ou levantado uma exceção, então o with substitui o try/finally escrito à mão para limpeza. Abra vários de uma vez com um único with. Lembre-se de que o passo de saída pode suprimir uma exceção, que é exatamente para isso que serve o contextlib.suppress, e vale reconhecer quando um bloco with está engolindo um erro.

Lendo arquivos ​

Três métodos para leitura. .read() carrega o arquivo inteiro como uma única string. .readline() lê uma linha. Iterar diretamente sobre o objeto de arquivo lê linha por linha, o que é a abordagem mais eficiente para arquivos grandes, já que não carrega tudo na memória de uma vez.

.read() carrega o arquivo inteiro na memória como uma única string. .readline() lê uma única linha, incluindo a quebra de linha. .readlines() retorna uma lista com todas as linhas. Para qualquer coisa que possa ser grande, itere o objeto de arquivo diretamente com for line in f: isso puxa uma linha por vez e nunca mantém o arquivo inteiro na memória, o que mantém o consumo de memória constante independentemente do tamanho do arquivo.

.read() retorna o arquivo inteiro como uma única string, então seu custo de memória escala com o tamanho do arquivo: tudo bem para uma configuração, um problema para um log de vários gigabytes. Iterar o objeto de arquivo, for line in f, lê uma linha por vez e a descarta antes da próxima, então a memória fica estável independentemente do tamanho. Esse é o padrão a adotar por padrão para arquivos de tamanho desconhecido. .readlines() parece conveniente, mas monta uma lista com todas as linhas de uma vez, o que tem o mesmo custo de memória do .read(), então não traz nenhuma vantagem sobre o laço. A regra que evita o clássico bug de memória esgotada: recorra primeiro ao laço for line in f, e só chame .read() quando souber que o arquivo é pequeno.

python
with open("data.txt", "r") as f:
    content = f.read()          # arquivo inteiro como uma única string

with open("data.txt", "r") as f:
    first_line = f.readline()   # uma linha por vez

with open("data.txt", "r") as f:
    lines = f.readlines()       # lista de linhas, cada uma terminando em "\n"

Para arquivos grandes, ler linha por linha é mais eficiente do que carregar tudo de uma vez:

python
with open("big_file.txt", "r") as f:
    for line in f:              # itera o arquivo diretamente, eficiente em memória
        print(line.strip())     # strip() remove a quebra de linha final

Iterar diretamente sobre o objeto de arquivo (for line in f) é a forma mais eficiente de ler um arquivo grande.

JunoLendo arquivos.read() te dá o arquivo inteiro como uma string, .readline() te dá uma única linha. Quando o arquivo pode ser grande, use um laço com for line in f, pois ele lê uma linha por vez e nunca carrega tudo na memória. O .strip() em cada linha remove a quebra de linha final.
JunoLendo arquivos.read() carrega tudo de uma vez, o que é aceitável para arquivos pequenos e arriscado para os grandes. Prefira for line in f por padrão: ele transmite uma linha por vez e mantém a memória constante independentemente do tamanho. .readlines() parece organizado, mas usa tanta memória quanto o .read().
JunoLendo arquivos.read() e .readlines() mantêm o arquivo inteiro na memória, então ambos escalam com o tamanho do arquivo. O laço for line in f lê e descarta uma linha por vez, mantendo a memória estável, e é isso que evita a surpresa de memória esgotada em um arquivo cujo tamanho você não checou antes. Use o laço por padrão, e o .read() só quando souber que é pequeno.

Escrevendo arquivos ​

O modo "w" sobrescreve o arquivo inteiro se ele existir. O modo "a" adiciona ao final. .write() não adiciona uma quebra de linha automaticamente; inclua "\n" explicitamente ao final de cada linha. Para escrever várias linhas de uma vez, junte-as com "\n".join().

.write(s) escreve uma string e, diferente do print(), não adiciona nada, nem espaço nem quebra de linha, então você mesmo inclui o "\n". .writelines(lines) escreve cada string de uma lista uma após a outra, também sem adicionar separadores, então as quebras de linha já precisam estar nas suas strings. Cuidado com o modo: "w" limpa o arquivo no instante em que é aberto, antes de você ter escrito qualquer coisa, então abrir com "w" quando você queria "a" descarta o conteúdo antigo.

Duas coisas sobre escrita que causam bugs reais. Primeiro, os dados que você escreve não necessariamente chegam ao disco imediatamente: o sistema operacional os mantém em um buffer e escreve em lotes, e é o .close() (que o with chama para você) que garante que tudo esteja no disco. Se um processo de longa duração escreve e nunca fecha o arquivo, o final da saída pode ficar preso no buffer e ser perdido em caso de travamento, então feche o arquivo ou chame .flush() nos pontos em que os dados precisam estar garantidamente salvos. Segundo, o modo decide o estrago: "w" esvazia o arquivo no momento em que é aberto, antes de qualquer escrita, então usar "w" quando você queria "a" destrói o conteúdo anterior sem aviso. .writelines(lines) não adiciona nenhum separador próprio, então as quebras de linha já precisam estar nas strings.

python
with open("output.txt", "w") as f:
    f.write("Hello, world\n")

with open("output.txt", "a") as f:
    f.write("Another line\n")

"w" sobrescreve o arquivo inteiro se ele existir. "a" adiciona ao final.

f.write() não adiciona uma quebra de linha automaticamente, então inclua "\n" explicitamente. Para escrever várias linhas de uma vez:

python
lines = ["Line one", "Line two", "Line three"]

with open("output.txt", "w") as f:
    f.write("\n".join(lines) + "\n")
JunoEscrevendo arquivos"w" cria ou sobrescreve o arquivo, "a" adiciona ao final sem apagá-lo. .write() não adiciona uma quebra de linha para você, então acrescente "\n" em cada linha você mesmo. Para um conjunto de linhas, "\n".join(lines) as junta antes.
JunoEscrevendo arquivos"w" limpa o arquivo no instante em que é aberto, "a" acrescenta, então escolher o errado descarta silenciosamente o conteúdo antigo. .write() não adiciona nada, nem quebra de linha, então você mesmo fornece o "\n". .writelines() escreve uma lista também sem separadores, as quebras de linha já precisam estar nas strings.
JunoEscrevendo arquivos"w" esvazia o arquivo no instante em que é aberto, então usá-lo em vez de "a" destrói o conteúdo anterior sem aviso. As escritas ficam em um buffer até que o .close() (que o with chama) as libere, então um processo que escreve e nunca fecha o arquivo pode perder o final em caso de travamento. .write() e .writelines() não adicionam quebras de linha, isso é responsabilidade sua.

Exceções ​

Quando o Python encontra um problema que não consegue tratar, ele levanta uma exceção: um erro que descreve o que deu errado e onde. Se você não a tratar, seu programa trava e imprime um traceback. A tabela abaixo mostra as exceções mais comuns que você vai encontrar.

Uma exceção é um objeto, e as exceções formam uma árvore genealógica: exceções específicas como FileNotFoundError são um tipo de uma mais ampla (OSError), e quase tudo que você captura é um tipo de Exception. Essa árvore é o que faz a captura funcionar: capturar um tipo pai também captura seus filhos. Quando uma exceção é levantada, o Python abandona a linha atual e sobe pela cadeia de chamadas que levou até ali, procurando um except compatível. Se não encontrar nenhum, o programa para e imprime um traceback: a lista de chamadas desde onde o erro aconteceu até o topo.

As exceções formam uma hierarquia de classes (uma árvore genealógica de tipos), e essa árvore é tudo o que importa quando você as captura: um except para um tipo pai também captura todos os tipos abaixo dele, então except Exception é uma rede ampla e except FileNotFoundError é uma rede estreita. O detalhe da árvore que vale memorizar é que KeyboardInterrupt (o usuário pressionando Ctrl-C) e SystemExit (um pedido de encerramento limpo) ficam ao lado de Exception, não abaixo dela. Isso é intencional: significa que um except Exception amplo deixa esses dois passarem, então um Ctrl-C ainda interrompe seu programa mesmo dentro de um bloco genérico. É exatamente por isso que você usa except Exception e nunca um except: sem tipo, que captura esses dois também e prende o usuário dentro de um programa do qual ele está tentando sair.

Exceções comuns que você vai encontrar:

ExceçãoQuando acontece
FileNotFoundErroropen() não encontra o arquivo
ValueErrorA função recebe um valor do tipo certo, mas com conteúdo errado, ex.: int("abc")
TypeErrorTipo completamente errado, ex.: "hello" + 5
KeyErrorA chave do dicionário não existe
IndexErrorÍndice da lista fora do intervalo
ZeroDivisionErrorDivisão por zero
AttributeErrorO objeto não tem esse atributo ou método
JunoExceções Uma exceção é o Python te avisando que algo deu errado. Você vai encontrar algumas constantemente: FileNotFoundError, ValueError, KeyError, TypeError. Se não tratadas, elas interrompem o programa e imprimem um traceback, que parece alarmante, mas na verdade é um mapa apontando exatamente para onde algo quebrou.
JunoExceções As exceções são objetos organizados em uma árvore genealógica, e capturar um tipo pai também captura seus filhos. Quando uma é levantada, o Python sobe pela cadeia de chamadas procurando um except compatível, e imprime um traceback se não encontrar nenhum. Conhecer os nomes comuns, ValueError, KeyError, FileNotFoundError, te ajuda a capturar o certo.
JunoExceções A hierarquia de tipos é o ponto central: except Exception é a rede ampla, um tipo específico é a rede estreita. KeyboardInterrupt e SystemExit ficam ao lado de Exception, não abaixo dela, então um Ctrl-C ainda escapa de except Exception. É por isso que você deve usá-lo em vez de um except: sem tipo, que prende o usuário dentro de um programa do qual ele está tentando sair.

try / except ​

Coloque código que pode falhar dentro de um bloco try. Se uma exceção ocorrer, o bloco except correspondente a trata em vez de deixar o programa travar. Seja específico sobre qual exceção você captura: capturar tudo com um except: sem tipo esconde bugs reais.

try/except captura um tipo específico de exceção e executa o tratador em vez de travar. Nomear o tipo importa: capture apenas aquele do qual você realmente consegue se recuperar, para que um bug não relacionado se manifeste em voz alta em vez de ser engolido. Vincule a exceção com as e quando quiser ler sua mensagem. Liste várias cláusulas except para tipos diferentes, e o Python usa a primeira que se encaixa.

Um except SomeType corresponde quando a exceção levantada é desse tipo ou de um subtipo dele (um tipo mais específico abaixo dele na árvore), e é por isso que capturar um tipo pai captura todos os seus filhos. O Python testa cada except de cima para baixo e para na primeira correspondência, então ordene do específico para o geral: uma cláusula ampla colocada primeiro captura tudo e as mais específicas abaixo dela nunca executam. A disciplina que impede o try/except de esconder bugs: capture o tipo mais específico que você consegue tratar, mantenha o corpo do try reduzido à única linha que pode de fato levantar a exceção, e deixe todo o resto se propagar. Um try envolvendo vinte linhas transforma uma falha esperada em uma máscara sobre dezenove inesperadas.

python
try:
    value = int("abc")
except ValueError:
    print("That's not a valid number")

Seja específico sobre qual exceção você captura. Capturar todas as exceções com um except: sem tipo esconde bugs:

python
# ruim, captura tudo, incluindo erros de programação
try:
    result = do_something()
except:
    pass

# bom, captura apenas o que você espera e consegue de fato tratar
try:
    result = do_something()
except FileNotFoundError:
    print("File not found")
Junotry / except Coloque código que pode falhar dentro de um bloco try, e o except correspondente executa em vez do programa travar. Nomeie a exceção que você espera, como ValueError, em vez de um except: sem tipo, que captura tudo e esconde bugs reais de você. Capture o que você realmente consegue lidar, e deixe o resto se mostrar.
Junotry / excepttry/except captura um tipo nomeado e se recupera em vez de travar; as e te dá a mensagem. Capture o tipo específico que você consegue tratar para que bugs não relacionados ainda apareçam. Mantenha o corpo do try pequeno, envolver vinte linhas transforma uma falha esperada em uma máscara sobre o resto.
Junotry / except Um except corresponde ao seu tipo e a qualquer subtipo, e o Python usa a primeira cláusula que se encaixa, então ordene do específico para o geral, ou a mais ampla vai eclipsar as demais. Capture o tipo mais específico, mantenha o try reduzido à linha que pode de fato levantar a exceção, e deixe todo o resto se propagar. Um try amplo esconde muito mais do que a falha que você pretendia tratar.

Capturando múltiplas exceções ​

Você pode tratar tipos diferentes de erro em blocos except separados, ou capturar vários tipos em um único bloco usando uma tupla. A parte as e te dá acesso à mensagem do erro.

Encadeie várias cláusulas except para tipos diferentes, e o Python as verifica de cima para baixo, usando a primeira que se encaixa. Para tratar vários tipos da mesma forma, agrupe-os em uma tupla: except (ValueError, ZeroDivisionError). as e vincula a exceção para que você possa ler sua mensagem. A ordem importa: coloque os tipos específicos antes dos gerais, ou uma cláusula ampla mais acima captura tudo e as que estão abaixo nunca executam.

A ordem é a armadilha. O Python usa o primeiro except cujo tipo corresponde, e um tipo amplo corresponde aos seus parentes mais específicos, então listar except Exception antes de except ValueError faz com que a cláusula ValueError seja código morto que nunca executa. Específico primeiro, geral por último. Agrupe tipos que compartilham uma resposta com except (A, B) as e. Quando você captura um erro só para adicionar contexto e repassá-lo, levante o novo com from: raise ValueError("bad config") from e. Isso mantém o erro original visível por baixo do novo no traceback, então o relatório mostra tanto o significado de alto nível quanto a causa de baixo nível em vez de substituir um pelo outro.

python
try:
    data = int(user_input)
    result = 100 / data
except ValueError:
    print("Not a number")
except ZeroDivisionError:
    print("Can't divide by zero")

Ou capture várias em uma tupla:

python
except (ValueError, ZeroDivisionError) as e:
    print(f"Input error: {e}")

as e vincula o objeto de exceção a um nome para que você possa inspecionar a mensagem.

JunoCapturando múltiplas exceções Dê a cada tipo de erro seu próprio bloco except, e o Python executa o primeiro que corresponder. Para tratar vários da mesma forma, liste-os em uma tupla: except (ValueError, ZeroDivisionError). A parte as e te entrega o objeto do erro para que você possa imprimir sua mensagem.
JunoCapturando múltiplas exceções Várias cláusulas except executam de cima para baixo, a primeira que corresponder vence, então liste tipos específicos antes dos gerais, ou o amplo eclipsa os demais. Uma tupla, except (A, B), trata vários com um único bloco. as e vincula a exceção para que você possa ler sua mensagem.
JunoCapturando múltiplas exceções Cláusulas específicas vêm primeiro: um tipo amplo acima de um estreito torna a cláusula estreita código morto. Agrupe respostas compartilhadas com except (A, B) as e. Quando você relançar para adicionar contexto, use raise NewError("...") from e para que a causa original continue visível sob o novo erro no traceback em vez de desaparecer.

else e finally ​

else executa apenas se nenhuma exceção ocorreu. finally sempre executa, tenha havido exceção ou não. finally é útil para limpeza que precisa acontecer independentemente do que aconteça.

else guarda o código que deve executar apenas quando o corpo do try não levantou nada. Mantê-lo fora do try deixa a intenção clara: você não está capturando exceções do caminho de sucesso por acidente. finally é a garantia de limpeza, ele executa não importa o quê, tenha o bloco tido sucesso, levantado uma exceção, sido capturada ou até mesmo atingido um return. É isso que faz dele o lugar certo para liberar um recurso que precisa ser liberado independentemente do resultado.

else executa apenas quando o corpo do try termina sem problemas, o que mantém o código do caminho de sucesso fora do try para que ele não acione acidentalmente uma das suas cláusulas except. finally executa incondicionalmente: depois de um sucesso, depois de uma exceção capturada, depois de uma não capturada em seu caminho para cima, até depois de um return no try. Esse último caso escondendo uma armadilha que vale conhecer: um return dentro de finally sobrescreve um return do try e, pior, engole silenciosamente uma exceção que estava se propagando, de forma que o erro desaparece sem deixar rastro. Mantenha o finally para limpeza, nunca dê return dentro dele. Para arquivos, o with já te dá essa garantia, então recorra ao finally principalmente quando estiver segurando um recurso que não tem gerenciador de contexto.

python
try:
    with open("data.txt") as f:
        content = f.read()
except FileNotFoundError:
    print("File not found, using defaults")
    content = ""
else:
    print("File loaded successfully")
finally:
    print("Done attempting to load file")   # sempre executa

finally é mais útil para limpeza (fechando conexões, liberando bloqueios), mesmo quando você já está usando with para arquivos.

Junoelse e finallyelse executa apenas quando o try não levantou nada, um lugar organizado para o restante do caminho de sucesso. finally executa sempre, com ou sem exceção, o que faz dele o lugar certo para a limpeza que precisa acontecer independentemente do resultado. Com arquivos, o with já cobre essa limpeza, então você vai usar o finally menos do que imagina.
Junoelse e finallyelse executa apenas no caminho limpo, mantendo o código de sucesso fora do try para que não dispare seu próprio except. finally executa incondicionalmente, até depois de um return, então é a sua garantia de limpeza. Para arquivos, o with já faz isso, então guarde o finally para recursos sem gerenciador de contexto.
Junoelse e finallyelse é o código do caminho limpo que fica fora do try para não disparar suas cláusulas except. finally executa não importa o quê, mas nunca dê return dentro dele: essa sobrescrita engole uma exceção em propagação e o erro desaparece silenciosamente. Mantenha o finally para limpeza, e principalmente para quando não há um with para se apoiar.

raise ​

Você pode levantar exceções você mesmo com raise. É assim que você faz suas funções sinalizarem problemas claramente para quem as chama, em vez de retornar silenciosamente um valor errado.

raise ExceptionType("message") lança uma exceção a partir do seu próprio código, e é assim que uma função relata uma entrada ruim em vez de seguir adiante com um valor errado. Dentro de um bloco except, um raise sozinho (sem argumento) relança a exceção que você capturou, útil quando você quer registrá-la ou reagir a ela e ainda deixá-la se propagar. Levantar exceções de forma deliberada torna os modos de falha de uma função parte do seu contrato, então quem chama pode capturá-los e tratá-los pelo nome.

raise SomeError("message") relata uma falha de propósito, a decisão certa quando uma função recebe uma entrada que não pode honrar: recusar em voz alta é melhor do que devolver uma resposta errada que se manifesta como um mistério mais tarde. Dentro de um except, um raise sozinho relança a exceção capturada com o traceback original intacto, então você pode registrá-la e relançá-la sem apagar de onde ela veio. Quando você quiser traduzir um erro de baixo nível em um erro de domínio, use from: raise ConfigError("bad settings") from e mantém o original por baixo do novo no relatório, então quem lê vê tanto o significado quanto a causa. A regra prática: relance para adicionar contexto, nunca para escondê-lo.

python
def divide(a, b):
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b

Isso torna suas funções explícitas sobre o que esperam e sinaliza problemas claramente para quem as chama.

Junoraiseraise ExceptionType("message") deixa seu próprio código sinalizar um problema em voz alta em vez de seguir adiante com um valor ruim. É assim que uma função diz "não consigo trabalhar com isso" para que quem a chamou lide com a situação. Claro e cedo é melhor do que uma resposta errada que aparece muito depois.
Junoraiseraise ExceptionType("message") relata uma falha a partir do seu código, tornando os casos de erro de uma função parte de como quem a chama a usa. Um raise sozinho dentro de um except relança o que você capturou, mantendo o traceback original, então você pode registrar e ainda deixá-lo se propagar.
Junoraise Levante exceções deliberadamente quando a entrada não puder ser honrada: recusar em voz alta é melhor do que uma resposta errada que se manifesta depois como um mistério. Um raise sozinho relança com o traceback original intacto. Para traduzir um erro de baixo nível em um erro de domínio, use raise DomainError("...") from e para que a causa continue visível, relance para adicionar contexto, nunca para enterrá-lo.

Classes de exceção personalizadas ​

Para programas maiores, você pode definir seus próprios tipos de exceção herdando de Exception. Isso permite que quem chama capture seus erros específicos separadamente de outros tipos de erro.

Uma exceção personalizada é sua própria classe que herda de Exception (ela se torna um novo tipo de exceção, então se encaixa na mesma árvore genealógica). O ganho é um nome que quem chama pode capturar com precisão, separado dos erros embutidos. Para um grupo de falhas relacionadas, crie uma classe base e derive uma subclasse para cada caso específico: quem chama captura a base para tratar o grupo inteiro, ou uma subclasse para isolar um caso.

Herde de Exception, não de BaseException (a raiz que também abrange Ctrl-C e encerramento, com os quais você não quer que seu erro fique agrupado). O motivo para definir seu próprio tipo em vez de reaproveitar ValueError é a árvore genealógica: construa uma base como PaymentError e derive uma subclasse para cada modo (InsufficientFundsError, CardDeclinedError), e quem chama pode capturar PaymentError para tratar a categoria inteira ou a subclasse para um caso específico, sem precisar interpretar strings de mensagem. Dê à classe um __init__ que carrega os campos relevantes (a conta, o valor) para que o tratador possa inspecionar o que aconteceu via código em vez de raspar o texto, o que transforma o erro em algo que um programa pode agir sobre, não apenas 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"Cannot withdraw {amount}, balance is {self.balance}"
            )
        self.balance -= amount
python
try:
    account.withdraw(1000)
except InsufficientFundsError as e:
    print(f"Transaction declined: {e}")
JunoClasses de exceção personalizadas Crie seu próprio tipo de erro herdando de Exception, e um único corpo de classe vazio já é suficiente para começar. Isso dá a quem chama um nome próprio seu para capturar, separado dos erros embutidos, então os problemas do seu programa ficam por conta própria.
JunoClasses de exceção personalizadas Uma exceção personalizada é uma classe que herda de Exception, dando a quem chama um nome preciso para capturar. Para falhas relacionadas, crie uma classe base e derive uma subclasse por caso: capture a base para o grupo inteiro, uma subclasse para um caso específico. Você pode adicionar campos para carregar detalhes além da mensagem em texto.
JunoClasses de exceção personalizadas Herde de Exception, não de BaseException, para que seu erro não fique misturado com Ctrl-C. O motivo para definir o seu próprio é a árvore: uma base PaymentError com subclasses específicas permite que quem chama trate a categoria ou um caso específico sem raspar texto de mensagem. Coloque os campos relevantes no __init__ para que os tratadores ajam sobre dados, não sobre strings.

JSON ​

JSON é o formato que todo mundo entende: APIs, arquivos de configuração, exportações de dados. O módulo json do Python trata isso diretamente. json.load() lê JSON de um arquivo em um dicionário ou lista do Python. json.dump() escreve um dicionário ou lista de volta em um arquivo como JSON.

Os pares se dividem pelo que tocam. json.load() / json.dump() trabalham com um objeto de arquivo, json.loads() / json.dumps() (os com o s) trabalham com uma string na memória. Passe indent=2 para dump/dumps quando um humano for ler a saída, caso contrário ela sai em uma única linha. JSON inválido levanta json.JSONDecodeError, que é um tipo de ValueError, então um tratador que captura ValueError já cobre isso.

Os quatro nomes são duas operações cruzadas com duas fontes: load/dump para arquivos, loads/dumps para strings (o s é de "string"). O detalhe que costuma pegar as pessoas em produção é o que o JSON não consegue representar. Ele não tem noção de data, set, ou Decimal, então json.dumps(datetime.now()) levanta TypeError. Você trata isso passando default=, uma função que dump/dumps chama para qualquer valor que não reconheça, e que deve retornar algo que o JSON consiga guardar, geralmente str(value). A outra regra que vale reter: números ida e volta se convertem em float, então um valor que você escreveu como Decimal para ter exatidão volta como float e perde essa precisão. Nunca armazene dinheiro ou qualquer coisa que precise de decimais exatos (veja o capítulo Numbers) como um número JSON comum.

Ler JSON de um arquivo:

python
import json

with open("config.json", "r") as f:
    config = json.load(f)    # converte JSON em um dict/list do Python

print(config["setting"])

Escrever JSON em um arquivo:

python
import json

data = {"name": "Ana", "score": 87, "active": True}

with open("output.json", "w") as f:
    json.dump(data, f, indent=2)    # indent= torna a saída legível para humanos

Mapeamento de tipos JSON para Python:

JSONPython
objeto {}dict
array []list
string ""str
númeroint ou float
true / falseTrue / False
nullNone

Para converter entre strings JSON e objetos Python sem tocar em um arquivo:

python
import json

# string para Python
data = json.loads('{"name": "Ana", "score": 87}')

# Python para string
text = json.dumps({"name": "Ana", "score": 87}, indent=2)

json.load() lê de um objeto de arquivo. json.loads() (com um "s") lê de uma string.

JunoJSONjson.load(f) transforma JSON de um arquivo em dicts e lists do Python, json.dump(data, f) escreve de volta. As versões com s, json.loads e json.dumps, fazem o mesmo para uma string em vez de um arquivo. Adicione indent=2 quando quiser a saída legível.
JunoJSONload/dump lidam com arquivos, loads/dumps lidam com strings, o s é de string. indent=2 torna a saída legível para humanos. JSON malformado levanta json.JSONDecodeError, que é um ValueError, então capturar ValueError já cobre uma falha de análise.
JunoJSON Duas operações cruzadas com duas fontes: load/dump para arquivos, loads/dumps para strings. JSON não tem data, set, nem Decimal, então gravar esses tipos levanta TypeError, a menos que você passe default=. E números fazem a ida e volta como float, então nunca armazene dinheiro como um número JSON comum, ele perde a exatidão no caminho de volta.

Na prática ​

Um padrão de salvar/carregar para um jogo pequeno: escrever o estado em JSON, carregá-lo de volta na próxima execução, e recorrer a valores padrão se ainda não existir um arquivo de save:

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("Game saved.")

def load_game() -> dict:
    try:
        with open(SAVE_FILE, "r") as f:
            return json.load(f)
    except FileNotFoundError:
        print("No save file found, starting fresh.")
        return {"name": "Player", "score": 0, "level": 1}

state = load_game()
state["score"] += 50
save_game(state)

Carregando um arquivo de configuração e salvando resultados, com tratamento específico de exceção para cada tipo de falha:

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"Config file not found: {path}")
    except json.JSONDecodeError as e:
        raise ValueError(f"Invalid JSON in {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"Saved {len(results)} result(s) to {path}")

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

Um registrador (logger) estruturado que acrescenta entradas com timestamp a um arquivo, com um tratador de nível superior que captura e registra falhas 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"Starting job, config: {config_path}")
    try:
        with open(config_path) as f:
            config = json.load(f)
        log("info", f"Loaded config: {config}")
    except FileNotFoundError:
        log("error", f"Config not found: {config_path}")
        raise
    except json.JSONDecodeError as e:
        log("error", f"Bad JSON in config: {e}")
        raise

try:
    process("config.json")
except Exception as e:
    log("critical", f"Job failed: {e}")

Relançar depois de registrar preserva o traceback original para quem chamou. O except Exception de nível superior captura qualquer coisa que tenha escapado, registra como crítico, e permite que o processo termine de forma limpa.