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 faz trabalho real interage com o sistema de arquivos: lê uma configuração, escreve resultados, carrega dados. E quando algo dá errado, Python lança uma exceção, um sinal de que algo inesperado aconteceu. Este capítulo cobre os dois temas: colocar dados dentro e fora de arquivos, e escrever código que trata erros de forma elegante em vez de travar.

E/S de arquivos e tratamento de exceções são os dois mecanismos que tornam programas robustos. open() dá acesso ao sistema de arquivos; with garante que a limpeza acontece mesmo em caso de erro. try/except captura tipos específicos de exceção, permitindo que você se recupere de forma elegante em vez de travar. Juntos, formam a base de qualquer script que roda sem supervisão.

Operações de arquivo em Python passam pela abstração de descritor de arquivo do SO. open() retorna um objeto de arquivo cujo tipo depende do modo e das configurações de buffer. Gerenciadores de contexto garantem a limpeza de recursos via __enter__ e __exit__. O tratamento de exceções usa desempilhamento estruturado: quando uma exceção se propaga, Python percorre a pilha de chamadas procurando cláusulas except correspondentes, executando blocos finally no caminho de saída independentemente.

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 deseja fazer com o arquivo (ler, escrever ou anexar). Sempre feche 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" para leitura, "w" para escrita (truncando primeiro), "a" para anexar. Adicionar "b" dá modo binário. A codificação padrão para modo texto é o locale do sistema; especifique encoding="utf-8" explicitamente para portabilidade.

open() chama o SO para alocar um descritor de arquivo e retorna um wrapper de E/S com buffer. O modo texto envolve com TextIOWrapper, que lida com codificação e quebras de linha universais. O modo binário retorna um BufferedReader ou BufferedWriter. O parâmetro encoding é crítico para portabilidade: o padrão (locale.getpreferredencoding()) difere por plataforma. Sempre especifique encoding="utf-8" para arquivos de texto, a menos que haja uma razão específica para não fazê-lo.

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

O "r" é o modo:

ModoSignificado
"r"Leitura. O arquivo deve existir. Modo padrão.
"w"Escrita. Cria ou sobrescreve o arquivo.
"a"Anexar. Adiciona ao final sem apagar.
"x"Criar. 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 disso deixa o arquivo bloqueado e pode causar corrupção de dados. A forma confiável de lidar com isso é a instrução with.

A instrução with

with open(...) gerencia o arquivo para você, fechando-o automaticamente quando o bloco indentado termina, mesmo que ocorra um erro. 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. Ele chama __enter__ no início e __exit__ no final, mesmo que uma exceção seja lançada. Para arquivos, __exit__ fecha o descritor de arquivo. Isso garante a limpeza sem exigir um wrapper try/finally em torno de cada acesso a arquivo.

with expr as name chama expr.__enter__() e vincula o resultado a name. Na saída (normal ou por exceção), expr.__exit__(exc_type, exc_val, tb) é chamado. Se __exit__ retornar truthy, a exceção é suprimida. Objetos de arquivo implementam este protocolo: __exit__ chama self.close(). Gerenciadores de contexto podem ser empilhados: with open(a) as f, open(b) as g: é idiomático para trabalhar com múltiplos arquivos.

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

# f é fechado aqui, garantidamente

O que o with faz?

with é a sintaxe de gerenciador de contexto do Python. Ele chama código de configuração e finalização para você; neste caso, abrindo e fechando o arquivo de forma confiável. Você não precisa saber como funciona internamente. Apenas use-o com open().

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, 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. .readline() lê uma linha incluindo o caractere de quebra de linha. .readlines() retorna uma lista de todas as linhas. Iterar o objeto de arquivo diretamente é o padrão mais eficiente em memória para arquivos grandes: lê uma linha de cada vez do buffer sem manter o conteúdo completo.

.read() lê até EOF, retornando o conteúdo como string (ou bytes em modo binário). .readline() lê até \n ou EOF. Iterar o objeto de arquivo chama __iter__, que chama readline() repetidamente: memória O(1), processamento linha por linha. .readlines() é equivalente a list(file) mas materializa todas as linhas de uma vez. Para arquivos muito grandes, o padrão de iterador é preferível a .read().

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 de cada 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 e idiomática de ler um arquivo grande.

Escrevendo arquivos

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

.write(s) escreve uma string e retorna o número de caracteres escritos. Não adiciona uma quebra de linha. .writelines(iterable) escreve cada string do iterável sem adicionar separadores. "w" trunca na abertura; "a" posiciona-se em EOF. Para portabilidade, use "\n" ou os.linesep em vez de codificar terminações de linha específicas de plataforma.

.write() escreve no buffer; os dados podem não chegar ao disco até que .flush() ou .close() seja chamado. O modo "w" chama truncate(0) na abertura, destruindo qualquer conteúdo anterior. O modo "a" posiciona-se em EOF antes de cada escrita, tornando-o seguro para anexadores concorrentes (na maioria dos SOs). f.writelines() chama f.write() para cada item: sem alocação extra de memória, mas também sem separadores adicionados.

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 inteiramente 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 = ["Linha um", "Linha dois", "Linha três"]

with open("output.txt", "w") as f:
    f.write("\n".join(lines) + "\n")

Exceções

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

Exceções são objetos que herdam de BaseException. Todas as exceções voltadas ao usuário herdam de Exception. Quando lançada, Python desempilha a pilha de chamadas procurando uma cláusula except correspondente. O traceback mostra a cadeia completa de chamadas do ponto da exceção até o ponto de entrada.

Objetos de exceção carregam tipo, mensagem e um atributo __traceback__ apontando para o objeto traceback. raise cria uma nova exceção; raise sem argumento re-lança a exceção atual preservando o traceback original. Encadeamento de exceções (raise B from A) liga duas exceções e é mostrado no traceback. BaseException é a raiz; KeyboardInterrupt e SystemExit herdam dela diretamente, não de Exception, e é por isso que um except Exception simples não as captura.

Exceções comuns que você encontrará:

ExceçãoQuando ocorre
FileNotFoundErroropen() não consegue encontrar o arquivo
ValueErrorA função recebe um valor do tipo certo mas conteúdo errado, ex.: int("abc")
TypeErrorTipo totalmente errado, 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 aquele atributo ou método

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 travar. Seja específico sobre qual exceção você captura: capturar tudo com um except: simples esconde bugs reais.

try/except intercepta tipos específicos de exceção. Especifique o tipo para evitar engolir silenciosamente erros não relacionados. Você pode vincular a exceção a um nome com as e para inspecionar a mensagem. Múltiplas cláusulas except tratam diferentes tipos de exceção; Python corresponde à primeira cláusula compatível.

except ExceptionType as e corresponde se isinstance(raised_exception, ExceptionType) for verdadeiro. Isso significa que capturar Exception também captura todas as subclasses. Python corresponde à primeira cláusula except cujo tipo corresponde; cláusulas subsequentes são ignoradas. except (A, B) as e captura qualquer um dos tipos. Um except: simples captura tudo, incluindo KeyboardInterrupt e SystemExit, o que quase nunca faz sentido.

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: simples esconde bugs:

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

# bom, captura apenas o que você espera e pode realmente tratar
try:
    result = do_something()
except FileNotFoundError:
    print("Arquivo não encontrado")

Capturando múltiplas exceções

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

Múltiplas cláusulas except são avaliadas de cima para baixo; a primeira correspondência vence. Capturar múltiplos tipos em uma tupla captura qualquer um deles. Usar as e vincula a instância da exceção, dando acesso à mensagem, tipo e traceback. Capturar o tipo mais específico primeiro e tipos mais gerais depois evita ocultação.

except (A, B) as e é açúcar sintático para uma única cláusula que captura ambos. A vinculação as e é limpa após a saída do bloco except (para evitar ciclos de referência com o traceback). Para re-lançar com contexto adicional, use raise ValueError("contexto") from e, que define __cause__ e mostra ambas as exceções no traceback.

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

else e finally

else roda apenas se nenhuma exceção ocorrer. finally sempre roda, independentemente de ter ocorrido ou não uma exceção. finally é útil para limpeza que precisa acontecer não importa o quê.

else separa o código de "caminho normal" do corpo do try, deixando claro que o código no else só roda quando nenhuma exceção foi lançada. finally é a garantia de limpeza: roda mesmo que uma exceção tenha sido lançada, capturada ou re-lançada. Roda até mesmo se return ou break for encontrado.

else roda se o bloco try foi concluído sem lançar exceção. finally roda incondicionalmente, incluindo após return, break, continue ou uma exceção não tratada. Se tanto finally quanto o código chamador tiverem instruções return, o return do finally tem precedência. finally com handles de arquivo ou conexões de banco de dados é uma rede de segurança secundária quando with não está disponível.

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("Tentativa de carregar arquivo concluída")   # sempre roda

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

raise

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

raise ExceptionType("mensagem") cria e lança uma exceção. raise sem argumento re-lança a exceção atual dentro de um bloco except. Lançar exceções a partir de suas funções torna suas condições de erro explícitas, permitindo que chamadores as tratem especificamente.

raise expr avalia expr para obter uma instância de exceção e define __traceback__. raise sozinho re-lança a exceção atual sem modificar o traceback. raise B from A define B.__cause__ = A (encadeamento explícito); raise B from None suprime a exibição de contexto. Após capturar e registrar, raise é a forma limpa de propagar preservando o traceback original.

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

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

Classes de exceção personalizadas

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

Exceções personalizadas criam uma hierarquia que chamadores podem capturar no nível certo de especificidade. Estender Exception geralmente é tudo o que você precisa. Para famílias de exceções, crie uma classe de exceção base e estenda-a para modos de erro específicos; chamadores podem então capturar o tipo base para tratar todas as variantes.

Exceções personalizadas devem estender Exception (não BaseException). Adicionar __init__ com campos específicos de domínio permite que chamadores inspecionem a exceção programaticamente, não apenas leiam a string de mensagem. Hierarquias de exceções permitem que chamadores escolham seu nível de especificidade: except PaymentError captura todos os erros relacionados a pagamento; except InsufficientFundsError captura apenas esse caso específico.

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"Não é possível sacar {amount}, saldo é {self.balance}"
            )
        self.balance -= amount
python
try:
    account.withdraw(1000)
except InsufficientFundsError as e:
    print(f"Transação recusada: {e}")

JSON

JSON é o formato que todo mundo entende: APIs, arquivos de configuração, exportações de dados. O módulo json do Python lida com ele diretamente. json.load() lê JSON de um arquivo em um dict ou list do Python. json.dump() escreve um dict ou list do Python em um arquivo como JSON.

json.load() e json.dump() trabalham com objetos de arquivo. json.loads() e json.dumps() trabalham com strings. O parâmetro indent= em dump/dumps torna a saída legível por humanos. json.JSONDecodeError (uma subclasse de ValueError) é lançada em JSON inválido.

json.load() é um parser de streaming: lê do objeto de arquivo incrementalmente. json.dumps() retorna uma string; json.dump() escreve em um objeto similar a arquivo que suporta .write(). O parâmetro default= de dump/dumps lida com tipos que não são serializáveis em JSON; recebe o objeto não serializável e deve retornar algo serializável. object_hook em load/loads intercepta cada dict de objeto analisado, permitindo desserialização personalizada.

Ler JSON de um arquivo:

python
import json

with open("config.json", "r") as f:
    config = json.load(f)    # analisa 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 legível por 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 mexer 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.

Na prática

Um padrão salvar/carregar para um jogo simples: escrever o estado em JSON, carregá-lo de volta na próxima execução e usar padrões caso ainda não exista 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("Jogo salvo.")

def load_game() -> dict:
    try:
        with open(SAVE_FILE, "r") as f:
            return json.load(f)
    except FileNotFoundError:
        print("Nenhum arquivo de save 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 específico de exceção 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"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 gravador de log estruturado que anexa entradas 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"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}")

Re-lançar após registrar preserva o traceback original para o chamador. 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.