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

Arquivos e exceções

docs.scrimba.com

A maioria dos programas que fazem trabalho real tocam o sistema de arquivos: lendo uma configuração, escrevendo resultados, carregando dados. E quando as coisas dão errado, Python lança uma exceção, um sinal de que algo inesperado aconteceu. Este capítulo cobre os dois: colocando dados dentro e fora de arquivos, e escrevendo código que trata erros com elegância em vez de falhar.

E/S de arquivo 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 fechado mesmo em caso de erro. try/except captura tipos de exceção específicos para que você possa se recuperar em vez de falhar. Juntos, eles são a base de qualquer script que executa sem supervisão.

Arquivos e exceções são onde um script encontra o mundo externo bagunçado: um caminho que não existe, um disco que fica cheio, entrada que não analisa. open() devolve um objeto de arquivo, e with garante que seja fechado mesmo quando o código dentro lança uma exceção. Exceções são como Python sinaliza falha: quando uma é lançada, ela viaja para cima através das chamadas que levaram a ela, e a primeira except correspondente no caminho a trata. A disciplina deste capítulo é sobre capturar as falhas específicas das quais você realmente pode se recuperar e deixar o resto vir à tona ruidosamente, em vez de engolir tudo e enviar um script que falha em silêncio.

Abrindo arquivos

open() abre um arquivo e retorna um objeto do qual você pode ler ou escrever. Você diz a ele o caminho e o que quer fazer com o arquivo (ler, escrever ou acrescentar). Sempre feche um arquivo quando terminar; a declaraçã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 primeiro), "a" acrescenta ao final. Adicione "b" para modo binário quando o arquivo não for texto. Uma coisa que vale a pena definir desde o dia um: passe encoding="utf-8" para arquivos de texto. Deixe de fora e Python usa o padrão da máquina, então um arquivo que lê bem no seu laptop pode voltar com caracteres quebrados no de outra pessoa.

open() pede ao SO um identificador no arquivo e retorna um objeto de arquivo envolvido nele. O modo escolhe tanto o acesso ("r", "w", "a") e se você obtém texto ou bytes brutos ("b"). O parâmetro que pega as pessoas é encoding: decide como bytes em disco mapeiam para caracteres. Deixe não definido e Python volta ao padrão de locale da máquina, que difere entre plataformas, então um arquivo escrito em uma máquina pode decodificar incorretamente em outra. Sempre passe encoding="utf-8" para texto a menos que tenha uma razão específica para não fazer, e as surpresas desaparecem principalmente.

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

O "r" é o modo:

ModoSignificado
"r"Ler. O arquivo deve existir. Modo padrão.
"w"Escrever. Cria ou sobrescreve o arquivo.
"a"Acrescentar. Adiciona ao final sem apagar.
"x"Criar. Falha se o arquivo já existe.
"r+"Ler e escrever.
"b"Binário. Adicione a qualquer modo: "rb", "wb".

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

JunoAbrindo arquivosopen(path, mode) lhe 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 estava lá), "a" para adicionar ao final. Perdi um arquivo para "w" uma vez ao alcançá-lo quando quis "a", então imagine o modo antes de digitá-lo.
JunoAbrindo arquivosopen(path, mode) retorna um objeto de arquivo: "r" ler, "w" sobrescrever, "a" acrescentar, mais "b" para binário. Passe encoding="utf-8" em arquivos de texto para que leiam igual em cada máquina, e deixe with lidar com o fechamento.
JunoAbrindo arquivosopen() lhe dá um objeto de arquivo sobre um identificador do SO; o modo define acesso e texto-versus-bytes. A configuração que viaja mal é encoding: deixe de fora e você herda o padrão de locale da máquina, então faça encoding="utf-8" o hábito para texto. Nunca se apoie em .close() à mão quando with fará por você.

A declaração with

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

with é a sintaxe de gerenciador de contexto do Python: um pequeno protocolo onde um objeto recebe uma chamada de "configuração" quando o bloco começa e uma chamada de "limpeza" quando termina. Para um arquivo, a configuração retorna o arquivo aberto e a limpeza o fecha. O ganho é que a limpeza executa mesmo se uma exceção é lançada dentro do bloco, então você obtém a limpeza de um try/finally sem escrever um ao redor de cada arquivo.

Um gerenciador de contexto (qualquer objeto que você possa colocar depois de with) define dois pedaços de comportamento: 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 Python executa quando o bloco termina, quer tenha terminado normalmente quer porque uma exceção foi lançada. Para arquivos, o passo de saída é close(). Duas notas práticas. Você pode abrir vários arquivos em uma instrução, with open(a) as f, open(b) as g:, e ambos fecham em ordem. E o passo de saída pode escolher engolir a exceção, que é como auxiliares como contextlib.suppress(FileNotFoundError) funcionam, então saiba que um bloco with pode absorver silenciosamente um erro se o gerenciador de contexto foi escrito para isso.

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

# f é fechado aqui, garantido

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

JunoA declaração withwith open(...) as f abre o arquivo, deixa você trabalhar com f dentro do bloco indentado, então o fecha para você quando o bloco termina, mesmo se algo der errado no meio. Alcance por ele cada vez em vez de chamar .close() você mesmo, é uma coisa a menos para esquecer.
JunoA declaração withwith executa configuração quando o bloco abre e limpeza quando fecha, e a limpeza acontece mesmo em uma exceção. Para um arquivo essa limpeza é close(), então você obtém segurança de `try`/`finally` sem escrever uma. Prefira por cima de `open()`/`close()` manual cada vez.
JunoA declaração with Um gerenciador de contexto fecha o arquivo na saída quer o bloco tenha terminado quer lançado, então `with` substitui `try`/`finally` escrito à mão para limpeza. Abra vários de uma vez com um `with`. Tenha em mente que o passo de saída pode suprimir uma exceção, que é exatamente para que `contextlib.suppress` é, e vale reconhecer quando um bloco `with` engole um erro.

Lendo arquivos

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

.read() carrega o arquivo todo em memória como uma string. .readline() lê uma única linha, quebra de linha incluída. .readlines() retorna uma lista de cada linha. Para qualquer coisa que pudesse ser grande, itere o objeto de arquivo diretamente com for line in f: puxa uma linha por vez e nunca mantém o arquivo cheio de uma vez, o que mantém a memória plana não importa o quão grande o arquivo fica.

.read() retorna o arquivo inteiro como uma 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 multi-gigabyte. Iterando o objeto de arquivo, for line in f, lê uma linha por passo e a descarta antes da próxima, então a memória fica plana independentemente do comprimento. Esse é o padrão para arquivos de tamanho desconhecido. .readlines() parece conveniente mas constrói uma lista de cada linha de uma vez, que tem a mesma pegada de memória de .read(), então não lhe compra nada sobre o loop. A regra que previne o bug clássico de memória insuficiente: alcance primeiro pelo loop for line in f, e apenas chame .read() quando souber que o arquivo é pequeno.

python
with open("data.txt", "r") as f:
    content = f.read()          # arquivo inteiro como uma 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 que carregar tudo de uma vez:

python
with open("big_file.txt", "r") as f:
    for line in f:              # itere 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() lhe dá o arquivo todo como uma string, .readline() lhe dá uma única linha. Quando o arquivo pudesse ser grande, faça loop com `for line in f`, ele lê uma linha por vez então você nunca carrega o lote todo em memória. O `.strip()` em cada linha limpa a quebra de linha final.
JunoLendo arquivos.read() carrega tudo de uma vez, o que é tudo bem para arquivos pequenos e arriscado para grandes. Padrão para `for line in f`: transmite uma linha por vez e mantém memória plana qualquer que seja o tamanho. `.readlines()` parece arrumado mas usa tanta memória quanto `.read()`.
JunoLendo arquivos.read() e .readlines() ambos mantêm o arquivo cheio em memória, então escalam com o tamanho do arquivo. O loop `for line in f` lê e descarta uma linha por vez, mantendo memória plana, que é o que impede a surpresa de memória insuficiente em um arquivo que você não dimensionou primeiro. Alcance pelo loop por padrão, `.read()` apenas quando souber que é pequeno.

Escrevendo arquivos

O modo "w" sobrescreve o arquivo inteiro se 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 múltiplas linhas de uma vez, junte-as com "\n".join().

.write(s) escreve uma string e, diferentemente de print(), não adiciona nada, sem espaço e sem quebra de linha, então você coloca o "\n" você mesmo. .writelines(lines) escreve cada string em uma lista de volta para trás, novamente sem separadores adicionados, então as quebras de linha já têm que estar em suas strings. Veja o modo: "w" limpa o arquivo no instante que o abre, antes que você tenha escrito uma coisa, então abrir com "w" quando você quis "a" descarta o conteúdo antigo.

Duas coisas sobre escrever que causam bugs reais. Primeiro, os dados que você escreve não necessariamente atingem o disco na mesma hora: o SO os mantém em um buffer e escreve em lotes, e é .close() (que with chama por você) que garante que tudo esteja em disco. Se um processo de longa duração escreve e nunca fecha, o final da saída pode ficar no buffer e ser perdido em um crash, então feche o arquivo ou chame .flush() nos pontos onde os dados têm que ser seguros. Segundo, o modo decide o dano: "w" esvazia o arquivo no momento que abre, antes de qualquer escrita, então alcançar por "w" quando você quis "a" destrói o conteúdo anterior sem aviso. .writelines(lines) não adiciona separadores de seus próprio, então as quebras de linha já têm que estar nas strings.

python
with open("output.txt", "w") as f:
    f.write("Olá, mundo\n")

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

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

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

python
lines = ["Linha um", "Linha dois", "Linha três"]

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 coloque "\n" ao final de cada linha você mesmo. Para um lote de linhas, "\n".join(lines) as une primeiro.
JunoEscrevendo arquivos"w" limpa o arquivo no momento que abre, "a" acrescenta, então escolher o errado silenciosamente joga fora o conteúdo antigo. .write() não adiciona nada, sem quebra de linha, então você fornece `"\n"` você mesmo. .writelines() escreve uma lista sem separadores também, as quebras de linha já têm que estar nas strings.
JunoEscrevendo arquivos"w" esvazia o arquivo no instante que abre, então alcançar por ele em vez de `"a"` destrói o conteúdo anterior sem aviso. Escritas ficam em um buffer até `.close()` (que `with` chama) as libera, então um processo que escreve e nunca fecha pode perder seu final em um crash. .write() e .writelines() não adicionam quebras de linha, isso é com você.

Exceções

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

Uma exceção é um objeto, e exceções formam uma árvore genealógica: 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 torna a captura funcionar: capturar um tipo pai também captura seus filhos. Quando uma é lançada, Python abandona a linha atual e sobe através das chamadas que levaram a ela, procurando um except correspondente. Se não encontrar nenhum, o programa para e imprime um traceback: a lista de chamadas de onde o erro aconteceu até o topo.

Exceções formam uma hierarquia de classe (uma árvore genealógica de tipos), e essa árvore é tudo no jogo quando você as captura: um except para um tipo pai também captura todo tipo abaixo dele, então except Exception é uma rede ampla e except FileNotFoundError é uma estreita. O pedaço da árvore que vale a pena memorizar é que KeyboardInterrupt (o usuário pressionando Ctrl-C) e SystemExit (uma solicitação de desligamento limpo) ficam ao lado de Exception, não sob ela. Isso é deliberado: significa que um amplo except Exception deixa essas duas passar, então um Ctrl-C ainda para seu programa mesmo dentro de uma captura geral. Isso é exatamente por que você alcança por except Exception e nunca um except: nu, que captura essas duas também e prende o usuário dentro de um programa que está tentando sair.

Exceções comuns que você encontrará:

ExceçãoQuando acontece
FileNotFoundErroropen() não consegue encontrar o arquivo
ValueErrorFunção recebe um valor do tipo certo mas conteúdo errado, por ex. int("abc")
TypeErrorTipo completamente errado, por ex. "hello" + 5
KeyErrorChave de dicionário não existe
IndexErrorÍndice de lista fora do intervalo
ZeroDivisionErrorDivisão por zero
AttributeErrorObjeto não tem esse atributo ou método
JunoExceções Uma exceção é Python lhe dizendo que algo deu errado. Você encontrará algumas constantemente: `FileNotFoundError`, `ValueError`, `KeyError`, `TypeError`. Deixadas sem tratamento elas param o programa e imprimem um traceback, que parece alarmante mas é realmente um mapa apontando direto para onde quebrou.
JunoExceções Exceções são objetos arranjados em uma árvore genealógica, e capturar um tipo pai também captura seus filhos. Quando uma é lançada Python sobe as chamadas procurando um `except` correspondente, e imprime um traceback se não encontrar nenhum. Saber os nomes comuns, `ValueError`, `KeyError`, `FileNotFoundError`, permite você capturar o certo.
JunoExceções A hierarquia de tipo é o todo ponto: `except Exception` é a rede ampla, um tipo específico é a estreita. `KeyboardInterrupt` e `SystemExit` ficam ao lado de `Exception`, não sob ela, então um Ctrl-C ainda escapa de `except Exception`. Essa é a razão para usá-lo sobre um `except:` nu, que prende o usuário dentro de um programa que está tentando sair.

try / except

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

try/except captura um tipo de exceção específico e executa o manipulador em vez de falhar. Nomear o tipo importa: capture o que você realmente pode se recuperar, então um bug não relacionado vem à tona ruidosamente em vez de ser engolido. Vincule a exceção com as e quando quer ler sua mensagem. Liste várias cláusulas except para tipos diferentes e Python usa a primeira que se encaixa.

Um except SomeType corresponde quando a exceção lançada é esse tipo ou um subtipo dele (um tipo mais específico sob ele na árvore), que é por que capturar um pai captura todos seus filhos. Python tenta cada except de cima para baixo e para no primeiro correspondente, então ordene-os específico antes do geral: uma cláusula ampla colocada primeiro capturará tudo e as mais estreitas abaixo dela nunca executam. A disciplina que mantém try/except de esconder bugs: capture o tipo mais estreito que você pode tratar, mantenha o corpo do try para baixo até a uma linha que realmente pode lançar, e deixe tudo o resto propagar. Um try envolvido ao redor de vinte linhas transforma uma falha esperada em uma máscara sobre dezenove inesperadas.

python
try:
    value = int("abc")
except ValueError:
    print("Isso não é um número válido")

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

python
# ruim, captura tudo inclusive erros do programador
try:
    result = do_something()
except:
    pass

# bom, apenas captura o que você espera e realmente pode tratar
try:
    result = do_something()
except FileNotFoundError:
    print("Arquivo não encontrado")
Junotry / except Coloque código que pode falhar em um bloco `try`, e o `except` correspondente executa em vez do programa falhar. Nomeie a exceção que você espera, como `ValueError`, em vez de um `except:` nu, que captura tudo e esconde bugs reais de você. Capture o que você pode realmente lidar, e deixe o resto mostrar.
Junotry / except `try`/`except` captura um tipo nomeado e se recupera em vez de falhar; `as e` lhe dá a mensagem. Capture o tipo específico que você pode tratar então bugs não relacionados ainda vêm à tona. 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 seu tipo e qualquer subtipo, e Python toma a primeira cláusula que se encaixa, então ordene específico antes do geral ou a ampla sombreia o resto. Capture o tipo mais estreito, mantenha o `try` para a linha que realmente pode lançar, e deixe tudo o resto propagar. Um `try` amplo esconde muito mais que a falha que você pretendia tratar.

Capturando múltiplas exceções

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

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

Python toma o primeiro except cujo tipo corresponde, e um tipo amplo corresponde seus parentes mais estreitos, então listar except Exception acima de except ValueError significa que a cláusula ValueError é 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 apenas para adicionar contexto e passá-lo adiante, lance o novo com from: raise ValueError("bad config") from e. Isso mantém o original visível sob o novo no traceback, então um 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("Não é um número")
except ZeroDivisionError:
    print("Não posso dividir por zero")

Ou capture múltiplos em uma tupla:

python
except (ValueError, ZeroDivisionError) as e:
    print(f"Erro de entrada: {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 Python executa o primeiro que corresponde. Para tratar alguns do mesmo jeito, liste-os em uma tupla: `except (ValueError, ZeroDivisionError)`. A parte `as e` lhe entrega o objeto de erro então você pode imprimir sua mensagem.
JunoCapturando múltiplas exceções Múltiplas cláusulas `except` executam de cima para baixo, primeiro correspondente vence, então liste tipos específicos antes de gerais ou a ampla sombreia o resto. Uma tupla, `except (A, B)`, trata vários com um bloco. `as e` vincula a exceção então você pode 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ça para adicionar contexto, use `raise NewError("...") from e` para que a causa original permaneça 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, quer haja uma exceção ou não. finally é útil para limpeza que deve acontecer não importa o quê.

else mantém o código que deve executar apenas quando o corpo do try não lançou nada. Mantendo-o 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, executa não importa o quê, quer o bloco tenha sucesso, lançado, sido capturado, ou até mesmo acertado um return. Isso é o que torna a casa certa para liberar um recurso que tem que ser deixado ir independentemente do resultado.

else executa apenas quando o corpo do try terminou limpo, que mantém o código do caminho de sucesso fora do try então não pode acidentalmente dispara uma de suas cláusulas except. finally executa incondicionalmente: após sucesso, após uma exceção capturada, após uma não capturada no caminho para cima, até mesmo após um return no try. Esse último caso esconde uma armadilha que vale a pena saber: um return dentro de finally sobrescreve um return do try e, pior, silenciosamente engole uma exceção que estava se propagando, então o erro desaparece sem deixar rastro. Mantenha finally para limpeza, nunca return dela. Para arquivos, with já lhe dá essa garantia, então alcance por finally principalmente quando você está mantendo um recurso que não tem um gerenciador de contexto.

python
try:
    with open("data.txt") as f:
        content = f.read()
except FileNotFoundError:
    print("Arquivo não encontrado, usando padrões")
    content = ""
else:
    print("Arquivo carregado com sucesso")
finally:
    print("Feito tentando carregar arquivo")   # sempre executa

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

Junoelse e finally `else` executa apenas quando o `try` não lançou nada, um lugar arrumado para o resto do caminho de sucesso. `finally` executa cada vez, exceção ou não, o que a torna o lugar para limpeza que tem que acontecer qualquer que seja o resultado. Com arquivos `with` já cobre a limpeza, então você alcançará `finally` menos do que pode esperar.
Junoelse e finally `else` executa apenas no caminho limpo, mantendo código de sucesso fora do `try` então não pode dispara seu próprio `except`. `finally` executa incondicionalmente, até mesmo além de um `return`, então é sua garantia de limpeza. Para arquivos `with` já faz isso, então guarde `finally` para recursos sem um gerenciador de contexto.
Junoelse e finally `else` é o código de caminho limpo que fica fora do `try` então não pode disparar suas cláusulas `except`. `finally` executa não importa o quê, mas nunca `return` dela: isso sobrescreve um `return` propagado e engole uma exceção propagante e o erro desaparece silenciosamente. Mantenha `finally` para limpeza, e principalmente apenas quando não há `with` para se apoiar.

raise

Você pode lançar exceções você mesmo com raise. Isso é como você faz suas funções sinalizarem problemas claramente aos chamadores em vez de silenciosamente retornar um valor errado.

raise ExceptionType("message") lança uma exceção do seu próprio código, que é como uma função relata uma entrada ruim em vez de coxear adiante com um valor errado. Dentro de um bloco except, um raise nu (sem argumento) relança a exceção que você capturou, útil quando quer registrar isso ou reagir e ainda deixá-la viajar para cima. Lançar deliberadamente torna os modos de falha de uma função parte de seu contrato, então chamadores podem capturar e tratá-los pelo nome.

raise SomeError("message") relata uma falha de propósito, o movimento certo quando uma função recebe entrada que não pode honrar: recusar ruidosamente bate retornar uma resposta errada que vem à tona como mistério depois. Dentro de um except, um raise nu relança a exceção que você capturou com seu traceback original intacto, então você pode registrar e relançar sem apagar onde veio. Quando você quer traduzir um erro de baixo nível em um de domínio, use from: raise ConfigError("bad settings") from e mantém o original sob o novo no relatório, então um leitor vê tanto o significado e a causa em vez de substituir um pelo outro. A regra de ouro: relance para adicionar contexto, nunca para escondê-lo.

python
def divide(a, b):
    if b == 0:
        raise ValueError("Não posso dividir por zero")
    return a / b

Isso torna suas funções explícitas sobre o que esperam e sinaliza problemas claramente aos chamadores.

Junoraiseraise ExceptionType("message") deixa seu próprio código sinalizar um problema em voz alta em vez de continuar com um valor ruim. É como uma função diz "Não posso trabalhar com isso" então o chamador lida com isso. Claro e cedo bate uma resposta errada que mostra muito depois.
Junoraiseraise ExceptionType("message") relata uma falha do seu código, tornando os casos de erro de uma função parte de como os chamadores a usam. Um `raise` nu dentro de um `except` relança o que você capturou, mantendo o traceback original, então você pode registrar e ainda deixá-lo viajar para cima.
Junoraise Lance deliberadamente quando entrada não pode ser honrada: recusar ruidosamente bate uma resposta errada que vem à tona depois como um mistério. Um `raise` nu relança com o traceback original intacto. Para traduzir um erro de baixo nível em um de domínio, use `raise DomainError("...") from e` para que a causa fico visível, relance para adicionar contexto, nunca para enterrá-lo.

Classes de exceção customizadas

Para programas maiores, você pode definir seus próprios tipos de exceção herdando de Exception. Isso deixa chamadores capturarem seus erros específicos separadamente de outros tipos de erros.

Uma exceção customizada é 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 pagamento é um nome que chamadores podem capturar precisamente, separado de erros embutidos. Para um grupo de falhas relacionadas, faça uma classe base e subclasse-a para cada caso específico: chamadores capturam a base para tratar o grupo todo, ou uma subclasse para uma única.

Subclasse Exception, não BaseException (a raiz que também cobre Ctrl-C e desligamento, que você não quer seu erro agrupado com). A razão para definir seu próprio tipo em vez de reusar ValueError é a árvore genealógica: construa uma base como PaymentError e subclasse-a para cada modo (InsufficientFundsError, CardDeclinedError), e um chamador pode capturar PaymentError para tratar a categoria toda ou a subclasse para um caso, sem analisar strings de mensagem. Dê à classe um __init__ carregando os campos relevantes (a conta, o montante) então o manipulador pode inspecionar o que aconteceu em código em vez de raspar o texto, que torna o erro algo um programa pode agir sobre, não apenas imprimir.

python
class SaldoInsuficienteError(Exception):
    pass

class ContaBancaria:
    def __init__(self, saldo):
        self.saldo = saldo

    def sacar(self, montante):
        if montante > self.saldo:
            raise SaldoInsuficienteError(
                f"Não posso sacar {montante}, saldo é {self.saldo}"
            )
        self.saldo -= montante
python
try:
    conta.sacar(1000)
except SaldoInsuficienteError as e:
    print(f"Transação recusada: {e}")
JunoClasses de exceção customizadas Faça seu próprio tipo de erro herdando de `Exception`, e um corpo de classe vazio é o suficiente para começar. Dá aos chamadores um nome para capturar que é seu, separado de erros embutidos, então os problemas do seu programa ficam por conta própria.
JunoClasses de exceção customizadas Uma exceção customizada é uma classe herdando de `Exception`, dando aos chamadores um nome preciso para capturar. Para falhas relacionadas, faça uma classe base e subclasse-a por caso: capture a base para o grupo todo, uma subclasse para um. Você pode adicionar campos para carregar detalhes além da string de mensagem.
JunoClasses de exceção customizadas Subclasse `Exception`, não `BaseException`, então seu erro não é agrupado com Ctrl-C. A razão para definir a sua é a árvore: uma base `PaymentError` com subclasses específicas deixa um chamador tratar a categoria ou um caso sem raspar texto de mensagem. Coloque os campos relevantes em `__init__` então manipuladores agem sobre dados, não strings.

JSON

JSON é o formato que tudo fala: APIs, arquivos de configuração, exportações de dados. O módulo json do Python o trata diretamente. json.load() lê JSON de um arquivo para um Python dicionário ou lista. json.dump() escreve um dicionário ou lista de volta para 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 lerá a saída, caso contrário vem em uma linha. JSON inválido lança json.JSONDecodeError, que é um tipo de ValueError, então um manipulador capturando 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 é "string"). O detalhe que pega as pessoas em produção é o que JSON não pode representar. Não tem noção de uma data, um set, ou um Decimal, então json.dumps(datetime.now()) lança TypeError. Você a trata passando default=, uma função dump/dumps chama em qualquer valor que não conhece, que deve retornar algo que JSON pode manter, normalmente str(value). A outra regra que vale manter: números ida e volta como float, então um valor que você escreveu como Decimal por exatidão volta como float e o perde. Nunca armazene dinheiro ou qualquer coisa que precise de decimais exatos (veja o capítulo Números) como um número JSON simples.

Leia JSON de um arquivo:

python
import json

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

print(config["setting"])

Escreva JSON para um arquivo:

python
import json

data = {"name": "João", "score": 87, "active": True}

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

Mapeamento de tipo JSON para Python:

JSONPython
object {}dict
array []list
string ""str
numberint or float
true / falseTrue / False
nullNone

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

python
import json

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

# Python para string
text = json.dumps({"name": "João", "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 em um arquivo em Python dicts e lists, json.dump(data, f) escreve-os de volta. As versões `s`, `json.loads` e `json.dumps`, fazem o mesmo para uma string em vez de um arquivo. Adicione `indent=2` quando quer a saída legível.
JunoJSONload/dump tratam arquivos, loads/dumps tratam strings, o `s` é para string. `indent=2` torna legível para humanos. JSON ruim lança `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, ou Decimal, então descarregar aqueles lança TypeError a menos que passe default=. E números ida e volta como float, então nunca armazene dinheiro como um número JSON simples, perde sua exatidão no caminho de volta.

Na prática

Um padrão de salvar/carregar para um pequeno jogo: escreva estado para JSON, carregue-o de volta na próxima execução, e caia em padrões se nenhum arquivo de salvamento existir ainda:

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("Jogo salvo.")

def load_game() -> dict:
    try:
        with open(SAVE_FILE, "r") as f:
            return json.load(f)
    except FileNotFoundError:
        print("Nenhum arquivo de salvamento encontrado, começando do zero.")
        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 de exceção específico para cada modo 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"Arquivo de configuração não encontrado: {path}")
    except json.JSONDecodeError as e:
        raise ValueError(f"JSON inválido em {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"Salvou {len(results)} resultado(s) para {path}")

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

Um escritor de log estruturado que acrescenta entradas marcadas com timestamp a um arquivo, com um manipulador 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"Iniciando tarefa, configuração: {config_path}")
    try:
        with open(config_path) as f:
            config = json.load(f)
        log("info", f"Configuração carregada: {config}")
    except FileNotFoundError:
        log("error", f"Configuração não encontrada: {config_path}")
        raise
    except json.JSONDecodeError as e:
        log("error", f"JSON ruim na configuração: {e}")
        raise

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

Relançar após registrar preserva o traceback original para o chamador. O except Exception de nível superior captura qualquer coisa que escapou, registra como crítico, e deixa o processo sair limpo.