Arquivos guardam bytes, não letras. O encoding é a tabela que diz qual byte vira qual caractere. Se quem grava e quem lê não combinarem a mesma tabela, os acentos e o ç saem corrompidos — ou o programa estoura um erro.
Em português, encoding não é detalhe — é a diferença entre "ação" e "ação".
O with abre o recurso, te entrega no as f, e ao sair do bloco chama o fechamento automaticamente — com erro ou sem erro. É exatamente o que o finally da aula 14 fazia, só que sem você precisar escrever o try/finally.
# antes (aula 14): manual
f = open("dados.txt", "w", encoding="utf-8")
try:
f.write("...")
finally:
f.close()
# agora: automático
with open("dados.txt", "w", encoding="utf-8") as f:
f.write("...") O with é o finally que você não precisa lembrar de escrever.
Um caminho relativo parte da pasta onde o programa está sendo executado (não de onde o arquivo .py está salvo). Um caminho absoluto é o endereço completo no disco, começando da raiz.
from pathlib import Path
caminho = Path("dados") / "pedidos.txt"
print(caminho) # dados/pedidos.txt
print(caminho.exists()) # True ou False Na memória, o objeto vive só enquanto o programa roda. Gravar o estado dele (os atributos) num arquivo faz o objeto voltar à vida numa execução futura. O segredo é converter objeto em dados de texto na hora de salvar, e reconstruir o objeto pelo construtor na hora de carregar.
def to_dict(self):
return {"cliente": self.cliente, "sabor": self.sabor, "valor": self.valor}
@classmethod
def from_dict(cls, dados):
return cls(dados["cliente"], dados["sabor"], dados["valor"]) Um objeto que sabe se salvar e se carregar é um objeto que sobrevive ao desligar do computador.
Até agora, tudo o que criamos morria quando o programa fechava. Hoje aprendemos a gravar e ler dados no disco — e a dar às nossas classes o poder de salvar e carregar seus objetos.
roteiro da aula
Nossos objetos somem quando o programa fecha. Precisamos de uma memória que sobreviva.
open(), os modos r/w/a, e o with — o jeito seguro de mexer em arquivos.
read() e write(), encoding utf-8 pros acentos, e o tratamento de arquivo que não existe.
Dar à classe um salvar() e um carregar() — e serializar em JSON.
ponte com a aula passada
o problema
Tudo o que criamos até agora vive só na memória RAM. Fechou o programa, perdeu.
class Pedido:
def __init__(self, cliente, sabor, valor):
self.cliente = cliente
self.sabor = sabor
self.valor = valor
pedido = Pedido("Ana", "Calabresa", 39.90)
print(pedido.cliente, pedido.sabor, pedido.valor) # Ana Calabresa 39.9
# Fim do programa... e o objeto some.
# Amanhã, ao rodar de novo, o pedido da Ana não existe mais.
# Ele viveu só na memória RAM — nada foi gravado em lugar nenhum. a ideia
abrir e fechar
open() recebe o caminho, o modo e o encoding, e devolve um objeto de arquivo pra você usar.
# open(caminho, modo, encoding) — abre o arquivo e devolve um objeto de arquivo
arquivo = open("pedidos.txt", "w", encoding="utf-8")
arquivo.write("Pedido da Ana: Calabresa - R$ 39,90")
arquivo.close() # fechar é OBRIGATÓRIO pra garantir que gravou
# O ritual de todo arquivo: abrir -> usar -> fechar.
# Esquecer o close() pode deixar o conteúdo "preso" sem ser gravado no disco. abrir e fechar
| item | detalhe |
|---|---|
| "r" (read) | Lê o arquivo. É o padrão. Levanta FileNotFoundError se o arquivo não existir. |
| "w" (write) | Escreve do zero. CUIDADO: apaga todo o conteúdo que já existia no arquivo! |
| "a" (append) | Anexa no final, preservando o que já estava lá. Cria o arquivo se não existir. |
| "x" (exclusive) | Cria um arquivo novo. Levanta erro se o arquivo já existir. |
| "+" | Combinado com os outros (ex.: "r+"), permite ler e escrever no mesmo arquivo. |
| "b" (binary) | Modo binário (ex.: "rb", "wb"), pra imagens e PDFs. Hoje ficamos só no texto. |
a pegadinha nº 1
O erro mais destrutivo de quem está começando: abrir em "w" achando que vai acrescentar — e apagar tudo.
# "w" apaga o que já estava no arquivo
with open("pedidos.txt", "w", encoding="utf-8") as f:
f.write("Ana: Calabresa\n")
with open("pedidos.txt", "w", encoding="utf-8") as f:
f.write("Bruno: Mussarela\n")
# conteúdo final do arquivo: APENAS
# Bruno: Mussarela
# o pedido da Ana foi apagado pelo segundo "w". # "a" preserva o que já estava e acrescenta no fim
with open("pedidos.txt", "a", encoding="utf-8") as f:
f.write("Ana: Calabresa\n")
with open("pedidos.txt", "a", encoding="utf-8") as f:
f.write("Bruno: Mussarela\n")
# conteúdo final do arquivo: as DUAS linhas
# Ana: Calabresa
# Bruno: Mussarela ler e escrever
write() grava exatamente o que você passar — nem uma quebra de linha a mais.
with open("pedidos.txt", "w", encoding="utf-8") as f:
f.write("Ana: Calabresa") # write NÃO adiciona quebra de linha
f.write("Bruno: Mussarela") # gruda na linha anterior!
# conteúdo gravado: Ana: CalabresaBruno: Mussarela <- tudo numa linha só
# A correção: você mesmo coloca o \n no fim de cada linha
with open("pedidos.txt", "w", encoding="utf-8") as f:
f.write("Ana: Calabresa\n")
f.write("Bruno: Mussarela\n")
# writelines grava uma lista de strings (também SEM \n automático)
linhas = ["Carla: Portuguesa\n", "Diego: Quatro Queijos\n"]
with open("pedidos.txt", "a", encoding="utf-8") as f:
f.writelines(linhas) ler e escrever
Quatro jeitos de ler. O último — iterar linha a linha — é o mais pythônico.
# read() — o arquivo inteiro como uma única string
with open("pedidos.txt", "r", encoding="utf-8") as f:
conteudo = f.read()
print(conteudo)
# Ana: Calabresa
# Bruno: Mussarela
# readline() — só a primeira linha (vem com o \n no fim)
with open("pedidos.txt", "r", encoding="utf-8") as f:
print(repr(f.readline())) # 'Ana: Calabresa\n'
# readlines() — uma lista com todas as linhas
with open("pedidos.txt", "r", encoding="utf-8") as f:
print(f.readlines()) # ['Ana: Calabresa\n', 'Bruno: Mussarela\n']
# o jeito pythônico — iterar linha a linha (.strip() tira o \n)
with open("pedidos.txt", "r", encoding="utf-8") as f:
for linha in f:
print(linha.strip()) # Ana: Calabresa / Bruno: Mussarela ler e escrever
Em português, sem encoding="utf-8" os acentos e o ç viram lixo — ou estouram um erro.
# Sem encoding, máquinas com configuração diferente podem gravar/ler errado.
# Com utf-8, acentos e ç são garantidos em qualquer computador.
with open("pedidos.txt", "w", encoding="utf-8") as f:
f.write("João pediu Calabresa\n")
f.write("Pizza de Coração de galinha\n")
with open("pedidos.txt", "r", encoding="utf-8") as f:
print(f.read())
# João pediu Calabresa
# Pizza de Coração de galinha
# Abrir sem o encoding certo pode estourar:
# UnicodeDecodeError: 'charmap' codec can't decode byte ... o with
Lembra do finally da aula 14? Dá pra garantir o close() com ele — mas é trabalhoso.
# O padrão da aula 14: garantir o close() no finally
f = open("pedidos.txt", "w", encoding="utf-8")
try:
f.write("Ana: Calabresa\n")
finally:
f.close() # roda SEMPRE — mesmo se der erro no write
# Funciona, mas é verboso: você precisa lembrar do try, do finally e do close.
# E se esquecer o close()? O arquivo pode ficar aberto e o dado não gravar. o with
O with abre o arquivo, te entrega no as f, e fecha sozinho ao sair do bloco.
# with cuida do close() sozinho — é o finally automático
with open("pedidos.txt", "w", encoding="utf-8") as f:
f.write("Ana: Calabresa\n")
# ao sair do bloco, o arquivo já está fechado. Mesmo se houvesse um erro.
# O que o finally fazia na unha (aula 14), o with faz de graça. o with
Mesmo resultado. À esquerda, você precisa lembrar de tudo. À direita, o with garante o fechamento.
f = open("pedidos.txt", "w", encoding="utf-8")
try:
f.write("Ana: Calabresa\n")
finally:
f.close()
# 5 linhas, e fácil de esquecer o close(). with open("pedidos.txt", "w", encoding="utf-8") as f:
f.write("Ana: Calabresa\n")
# 2 linhas. O arquivo fecha sozinho,
# sem risco de esquecer. tratando erros
Ler um arquivo que não existe levanta FileNotFoundError. Esse é o caso de uso natural do try/except (aula 14).
# Ler um arquivo que não existe levanta FileNotFoundError (aula 14!)
try:
with open("caixa_de_ontem.txt", "r", encoding="utf-8") as f:
print(f.read())
except FileNotFoundError:
print("Arquivo não encontrado — começando um caixa novo.")
# Arquivo não encontrado — começando um caixa novo.
# Outro erro comum: PermissionError (sem permissão pra abrir/gravar o arquivo). tratando erros
"O arquivo foi salvo ONDE?" — caminho relativo parte de onde o programa roda; absoluto é o endereço completo.
from pathlib import Path
caminho = Path("dados") / "pedidos.txt" # monta o caminho sem se preocupar com / ou \
print(caminho) # dados/pedidos.txt
print(caminho.exists()) # False <- dá pra checar ANTES de tentar abrir
# Caminho relativo: parte da pasta onde o programa roda (ex.: dados/pedidos.txt).
# Caminho absoluto: o endereço completo no disco (ex.: /Users/leo/pizzaria/dados/pedidos.txt).
# Path("pedidos.txt").absolute() devolve esse caminho absoluto. a ponte com POO
salvar objetos
Damos à classe Pedido um método salvar(): ele grava os próprios atributos como uma linha de texto.
class Pedido:
def __init__(self, cliente, sabor, valor):
self.cliente = cliente
self.sabor = sabor
self.valor = valor
def salvar(self, caminho="pedidos.txt"):
# grava o estado do objeto como uma linha de texto (anexando)
with open(caminho, "a", encoding="utf-8") as f:
f.write(f"{self.cliente};{self.sabor};{self.valor}\n")
pedido = Pedido("Ana", "Calabresa", 39.90)
pedido.salvar()
# o arquivo pedidos.txt agora tem a linha: Ana;Calabresa;39.9 salvar objetos
carregar() lê a linha e devolve um Pedido novo — é o factory method (@classmethod) da aula 03.
class Pedido:
def __init__(self, cliente, sabor, valor):
self.cliente = cliente
self.sabor = sabor
self.valor = valor
@classmethod
def carregar(cls, caminho="pedidos.txt"):
# lê a primeira linha e reconstrói um Pedido (factory method, aula 03)
with open(caminho, "r", encoding="utf-8") as f:
linha = f.readline().strip()
cliente, sabor, valor = linha.split(";")
return cls(cliente, sabor, float(valor))
pedido = Pedido.carregar()
print(pedido.cliente, pedido.sabor, pedido.valor) # Ana Calabresa 39.9
# o objeto voltou à vida — direto do arquivo. salvar objetos
JSON guarda dicionários e listas. Convertemos o objeto num dict (to_dict) pra gravar, e remontamos (from_dict) pra ler.
import json
class Pedido:
def __init__(self, cliente, sabor, valor):
self.cliente = cliente
self.sabor = sabor
self.valor = valor
def to_dict(self):
return {"cliente": self.cliente, "sabor": self.sabor, "valor": self.valor}
@classmethod
def from_dict(cls, dados):
return cls(dados["cliente"], dados["sabor"], dados["valor"])
# salvar em JSON
pedido = Pedido("João", "Coração de Frango", 39.90)
with open("pedido.json", "w", encoding="utf-8") as f:
json.dump(pedido.to_dict(), f, ensure_ascii=False, indent=2)
# ler de volta
with open("pedido.json", "r", encoding="utf-8") as f:
dados = json.load(f)
pedido = Pedido.from_dict(dados)
print(pedido.cliente, pedido.sabor, pedido.valor) # João Coração de Frango 39.9 ao vivo · vamos construir juntos
Vou construir esse na frente de vocês, do zero. Objetivo: um Pedido que sabe se salvar() no arquivo, e um carregar_todos() que traz a lista de volta — mesmo que o arquivo ainda não exista. Acompanhem o raciocínio passo a passo.
Com cliente, sabor e valor, e um __str__ que mostre o pedido formatado (ex.: Ana — Calabresa — R$ 39,90).
Método salvar(self, caminho="pedidos.txt") que abre no modo "a" com encoding="utf-8" e grava os campos numa linha separada por ;.
@classmethod carregar_todos(cls, caminho="pedidos.txt") que lê o arquivo iterando linha a linha e devolve uma lista de objetos Pedido.
Se o arquivo ainda não existir, o carregar_todos() deve devolver uma lista vazia em vez de quebrar — use try/except FileNotFoundError (aula 14).
resolução · passo 1
Começamos pela classe: três atributos e um __str__ que devolve o pedido já formatado, pronto pra exibir.
:.2f formata o número com 2 casas (39.90); o .replace troca o ponto pela vírgula do real.__str__ (aula 03) deixa o objeto pronto pro print() — a string de exibição mora dentro da classe.class Pedido:
def __init__(self, cliente, sabor, valor):
self.cliente = cliente
self.sabor = sabor
self.valor = valor
def __str__(self):
preco = f"R$ {self.valor:.2f}".replace(".", ",")
return f"{self.cliente} — {self.sabor} — {preco}"
print(Pedido("João", "Coração de Frango", 39.90)) # João — Coração de Frango — R$ 39,90 resolução · passo 2
Cada Pedido sabe se gravar. O modo "a" anexa no fim — não apaga o que já estava lá.
"a" (append) escreve no fim do arquivo. Com "w", cada chamada apagaria o pedido anterior.; e a linha fecha com \n — um pedido por linha, pra reler depois.with fecha o arquivo sozinho; o encoding="utf-8" preserva o ç e os acentos. # (dentro da classe Pedido, logo abaixo do __str__)
def salvar(self, caminho="pedidos.txt"):
with open(caminho, "a", encoding="utf-8") as f:
f.write(f"{self.cliente};{self.sabor};{self.valor}\n")
# cada pedido grava uma linha sua
Pedido("João", "Coração de Frango", 39.90).salvar()
Pedido("Ana", "Calabresa", 49.90).salvar() resolução · passos 3 e 4
Um @classmethod (aula 03) que lê o arquivo linha a linha, reconstrói a lista de objetos — e não quebra se o arquivo ainda não existir.
split(";") desmonta a linha nos três campos; float(valor) volta a ser número (no arquivo tudo é texto).cls(...) cria um Pedido — o factory method da aula 03, aqui devolvendo a lista inteira.try/except FileNotFoundError (aula 14) cobre a 1ª execução: sem arquivo, devolve [] em vez de explodir. @classmethod
def carregar_todos(cls, caminho="pedidos.txt"):
pedidos = []
try:
with open(caminho, "r", encoding="utf-8") as f:
for linha in f:
cliente, sabor, valor = linha.strip().split(";")
pedidos.append(cls(cliente, sabor, float(valor)))
except FileNotFoundError:
pass # arquivo ainda não existe → começamos do zero
return pedidos resolução · saída esperada
Na 1ª execução não há arquivo: carregamos (vazio), gravamos dois pedidos e listamos de volta.
pedidos.txt o valor fica como 39.9 (texto), mas o __str__ exibe R$ 39,90 — quem formata é a classe, não o arquivo.# 1ª execução: ainda não há arquivo → carregar_todos() devolve []
anteriores = Pedido.carregar_todos()
print(f"Pedidos já salvos: {len(anteriores)}") # Pedidos já salvos: 0
Pedido("João", "Coração de Frango", 39.90).salvar()
Pedido("Ana", "Calabresa", 49.90).salvar()
print("--- Pedidos no caixa ---")
for pedido in Pedido.carregar_todos():
print(pedido)
# --- Pedidos no caixa ---
# João — Coração de Frango — R$ 39,90
# Ana — Calabresa — R$ 49,90 resolução · arquivo completo
Os quatro passos montados num arquivo só — a classe com seus três métodos e o programa que usa.
class Pedido:
def __init__(self, cliente, sabor, valor):
self.cliente = cliente
self.sabor = sabor
self.valor = valor
def __str__(self):
preco = f"R$ {self.valor:.2f}".replace(".", ",")
return f"{self.cliente} — {self.sabor} — {preco}"
def salvar(self, caminho="pedidos.txt"):
with open(caminho, "a", encoding="utf-8") as f:
f.write(f"{self.cliente};{self.sabor};{self.valor}\n")
@classmethod
def carregar_todos(cls, caminho="pedidos.txt"):
pedidos = []
try:
with open(caminho, "r", encoding="utf-8") as f:
for linha in f:
cliente, sabor, valor = linha.strip().split(";")
pedidos.append(cls(cliente, sabor, float(valor)))
except FileNotFoundError:
pass # arquivo ainda não existe → começamos do zero
return pedidos
# ── Programa ───────────────────────────────────────────
anteriores = Pedido.carregar_todos()
print(f"Pedidos já salvos: {len(anteriores)}")
Pedido("João", "Coração de Frango", 39.90).salvar()
Pedido("Ana", "Calabresa", 49.90).salvar()
print("--- Pedidos no caixa ---")
for pedido in Pedido.carregar_todos():
print(pedido) recapitulando
| item | detalhe |
|---|---|
| open(caminho, modo, encoding) | Abre um arquivo e devolve um objeto de arquivo. |
| modo r / w / a | Ler / escrever do zero (apaga!) / anexar no fim. |
| with open(...) as f: | Abre e fecha o arquivo automaticamente. O jeito recomendado. |
| encoding="utf-8" | Garante acentos e ç. Obrigatório no nosso conteúdo em português. |
| write() / read() | Escreve uma string / lê o conteúdo. write não pula linha sozinho. |
| FileNotFoundError | Erro ao abrir em "r" um arquivo que não existe. Trate com try/except. |
| json.dump / json.load | Grava um dict/list em arquivo / lê de volta. Serialização. |
exercícios · visão geral
Cinco exercícios em três níveis. Começamos juntos agora, em sala — quem não fechar todos, termina em casa. Mire pelo menos um de cada nível antes da próxima aula.
2 exercícios — Diário de bordo e Contador de linhas. Foco: open/with, modos a/r e leitura linha a linha.
2 exercícios — Leitura segura e Coleção de jogos. Foco: try/except FileNotFoundError e a ponte arquivo + POO.
1 exercício — Cardápio em JSON. Foco: serializar uma lista inteira de objetos com json.dump/json.load.
exercícios · nível fácil
Crie um diario.py que anexa (modo "a") uma linha nova a um log.txt a cada execução — por exemplo, a data e hora atual com datetime.now() —, sempre com encoding="utf-8". Depois leia tudo iterando for linha in f. Foco: o modo a não apaga, e o \n é por sua conta.
Dica: agora = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
Leia o log.txt gerado no exercício anterior (ou crie um arquivo com algumas linhas de texto) e conte quantas linhas e quantas palavras ele tem, usando readlines() e .split(). Foco: as formas de ler e iterar o conteúdo.
exercícios · nível médio
Função ler_config(caminho) que tenta abrir o arquivo em "r" e devolve o conteúdo lido; se ele não existir, captura FileNotFoundError, cria o arquivo com um conteúdo padrão (ex.: tema=escuro) e tenta de novo. Foco: o try/except FileNotFoundError da aula 14 aplicado a arquivos.
Classe Jogo com nome, categoria e num_jogadores, um __str__ formatado e um salvar(self, caminho="jogos.txt") que anexa (modo "a", encoding="utf-8") os campos separados por ;. Um @classmethod carregar_todos() lê o arquivo linha a linha e devolve uma lista de objetos Jogo, tratando com try/except FileNotFoundError o arquivo que ainda não existe. Foco: reproduzir de memória o padrão do exercício ao vivo, num domínio novo. Atenção: num_jogadores é inteiro — na hora de carregar é int(...), não float(...).
exercícios · nível difícil
Classe Pizza com sabor, tamanho e preco, mais to_dict()/from_dict(). Uma classe Cardapio guarda uma lista de pizzas, com um adicionar(pizza) pra incluir cada uma, um salvar(caminho) que grava a lista inteira com json.dump e um @classmethod carregar(caminho) que lê com json.load, reconstrói cada Pizza e devolve um Cardapio — tratando com try/except FileNotFoundError o arquivo que ainda não existe. Foco: serializar uma coleção de objetos — exatamente a ponte pra aula 18. Atenção: json.dump grava dicionários, não objetos.
Hoje salvamos um objeto por vez. Na próxima, vamos guardar VÁRIOS com elegância: list, dict, set e tuple — e como as suas classes conversam com for, in e sorted.