Módulo 16 - Projeto final: agenda de contatos
Exportar para CSV e o menu
13 min de leitura · por Cesar Gargiulo, revisado pela equipe ValorFinal e GuardiaSec · Atualizado em 01/07/2026
O que você vai aprender
- Exportar os contatos para um arquivo CSV com o módulo csv.
- Montar o menu principal com um laço while.
- Tratar erros e entradas inválidas dentro do menu.
- Reunir o programa completo em um único arquivo comentado.
Ouvir o resumo desta aula
Um recap de cerca de 2 minutos na voz do Valim, para ouvir no trânsito ou na academia.
Ler a transcrição do resumo
Resumo da aula: Exportar para CSV e o menu.
Os objetivos desta aula. Exportar os contatos para um arquivo CSV com o módulo csv. Montar o menu principal com um laço while. Tratar erros e entradas inválidas dentro do menu. Reunir o programa completo em um único arquivo comentado.
Veja o essencial, parte por parte.
Exportar para CSV. exportar_csv grava os contatos em um arquivo que abre em planilhas.
O menu que costura tudo. O menu é onde as peças se encontram.
O programa completo. Aqui está o arquivo agenda.py inteiro, com tudo que você construiu ao longo das aulas em um só lugar, comentado.
Esse foi o resumo do essencial. Para se aprofundar, leia a aula completa e responda os exercícios.
Exportar para CSV
O JSON serve para o programa guardar e recarregar os dados. O CSV serve para levar os dados para fora, para uma planilha. São propósitos diferentes, e por isso a exportação é um método separado. O módulo csv da biblioteca padrão cuida dos detalhes chatos, como colocar aspas quando um campo tem vírgula. Você cria um escritor, grava uma linha de cabeçalho com os nomes das colunas e depois uma linha por contato. O parâmetro newline vazio evita linhas em branco extras no arquivo, uma recomendação da própria documentação.
import csv
class Agenda:
# ... demais metodos como antes ...
def exportar_csv(self, caminho):
with open(caminho, "w", newline="", encoding="utf-8") as arquivo:
escritor = csv.writer(arquivo)
escritor.writerow(["nome", "telefone", "email"])
for contato in self.contatos:
escritor.writerow([contato.nome, contato.telefone, contato.email])exportar_csv grava um cabeçalho e uma linha por contato, pronto para planilha.
Depois de exportar, abra o arquivo contatos.csv em uma planilha e veja os contatos organizados em colunas. Repare que aqui não há para_dicionario: o CSV é uma tabela simples, então basta uma lista com os três valores por linha, na mesma ordem do cabeçalho. Diferente do JSON, o CSV não é feito para o programa ler de volta e reconstruir objetos; ele é um ponto final, uma saída para o mundo das planilhas. Por isso não escrevemos um importar_csv: a fonte de verdade do programa continua sendo o contatos.json.
O programa completo
Aqui está o arquivo agenda.py inteiro, com tudo que você construiu ao longo das aulas em um só lugar, comentado. É a hora de comparar com o seu código e conferir se nada ficou para trás. Salve, rode e use a agenda de verdade: adicione contatos, feche o programa, abra de novo e veja que eles continuam lá. Você acabou de escrever um programa completo, com classes, persistência, exportação e tratamento de erros.
# agenda.py - Agenda de contatos de linha de comando
import json
import csv
import re
# Padrao de telefone: 10 ou 11 digitos, do inicio ao fim.
TELEFONE_RE = re.compile(r"^\d{10,11}$")
def telefone_valido(telefone):
"""Devolve True se o telefone tiver 10 ou 11 digitos e nada mais."""
return TELEFONE_RE.match(telefone) is not None
class ContatoNaoEncontradoError(Exception):
"""Levantada quando um contato nao existe na agenda."""
class Contato:
def __init__(self, nome, telefone, email=""):
self.nome = nome
self.telefone = telefone
self.email = email
def __str__(self):
return f"{self.nome} | {self.telefone} | {self.email}"
def para_dicionario(self):
# Traduz o objeto para um dicionario salvavel em JSON.
return {"nome": self.nome, "telefone": self.telefone, "email": self.email}
@classmethod
def de_dicionario(cls, dados):
# Reconstroi um Contato a partir de um dicionario lido do JSON.
return cls(dados["nome"], dados["telefone"], dados.get("email", ""))
class Agenda:
def __init__(self):
self.contatos = []
def adicionar_contato(self, contato):
self.contatos.append(contato)
def listar(self):
return list(self.contatos)
def buscar_por_nome(self, nome):
for contato in self.contatos:
if nome.lower() in contato.nome.lower():
return contato
raise ContatoNaoEncontradoError(nome)
def remover(self, nome):
contato = self.buscar_por_nome(nome)
self.contatos.remove(contato)
def salvar_json(self, caminho):
dados = [contato.para_dicionario() for contato in self.contatos]
with open(caminho, "w", encoding="utf-8") as arquivo:
json.dump(dados, arquivo, ensure_ascii=False, indent=2)
@classmethod
def carregar_json(cls, caminho):
agenda = cls()
with open(caminho, encoding="utf-8") as arquivo:
dados = json.load(arquivo)
for item in dados:
agenda.adicionar_contato(Contato.de_dicionario(item))
return agenda
def exportar_csv(self, caminho):
with open(caminho, "w", newline="", encoding="utf-8") as arquivo:
escritor = csv.writer(arquivo)
escritor.writerow(["nome", "telefone", "email"])
for contato in self.contatos:
escritor.writerow([contato.nome, contato.telefone, contato.email])
def menu():
try:
agenda = Agenda.carregar_json("contatos.json")
except FileNotFoundError:
agenda = Agenda()
while True:
print("\n1) Adicionar 2) Listar 3) Buscar 4) Remover 5) Exportar CSV 0) Sair")
opcao = input("Opcao: ").strip()
if opcao == "1":
nome = input("Nome: ").strip()
telefone = input("Telefone (so numeros): ").strip()
if not telefone_valido(telefone):
print("Telefone invalido. Use 10 ou 11 digitos.")
continue
email = input("E-mail: ").strip()
agenda.adicionar_contato(Contato(nome, telefone, email))
agenda.salvar_json("contatos.json")
print("Contato adicionado.")
elif opcao == "2":
for contato in agenda.listar():
print(contato)
elif opcao == "3":
nome = input("Nome a buscar: ").strip()
try:
print(agenda.buscar_por_nome(nome))
except ContatoNaoEncontradoError:
print(f"Nao encontrei '{nome}'.")
elif opcao == "4":
nome = input("Nome a remover: ").strip()
try:
agenda.remover(nome)
agenda.salvar_json("contatos.json")
print("Contato removido.")
except ContatoNaoEncontradoError:
print(f"Nao encontrei '{nome}'.")
elif opcao == "5":
agenda.exportar_csv("contatos.csv")
print("Exportado para contatos.csv.")
elif opcao == "0":
print("Ate mais!")
break
else:
print("Opcao invalida.")
if __name__ == "__main__":
menu()O arquivo agenda.py completo: classes, JSON, CSV, exceção e menu, tudo junto.
Teste rápido
Por que o programa salva o JSON logo após adicionar ou remover um contato?
Perguntas frequentes
- Por que não existe um método importar_csv, se há exportar_csv?
- Porque o CSV é uma saída para planilhas, não a fonte de verdade do programa. Quem guarda e recarrega os dados é o JSON, feito para reconstruir os objetos. Importar de CSV seria possível, mas ambíguo, já que o CSV não distingue tipos. Deixamos o JSON como formato interno único.
- O que faz o newline vazio no open ao gravar CSV?
- Evita linhas em branco extras entre os registros em alguns sistemas, principalmente no Windows. A documentação do módulo csv recomenda abrir o arquivo com newline vazio justamente por isso. É um detalhe pequeno que evita um arquivo com espaçamento estranho.
- Para que serve o if __name__ == "__main__" no fim?
- Ele faz o menu rodar apenas quando você executa o arquivo diretamente. Se outro arquivo importar agenda.py, por exemplo os testes, o menu não dispara sozinho. É o que permite reaproveitar as classes sem abrir o menu, essencial para a aula de testes.
- Por que o telefone inválido usa continue em vez de encerrar?
- Porque continue apenas cancela a inclusão daquele contato e volta ao menu, deixando o programa vivo para o usuário tentar de novo. Encerrar seria drástico demais para um simples erro de digitação. O programa avisa o problema e segue funcionando.
- Posso adicionar uma opção de editar contato ao menu?
- Pode, e é um ótimo próximo passo. Você buscaria o contato por nome, pediria os novos dados e atualizaria os atributos, salvando em seguida. O menu já tem a estrutura de elif pronta para receber a nova opção. É um dos exercícios sugeridos na aula final.
- O menu trata todos os erros possíveis?
- Ele trata os principais do fluxo normal: contato não encontrado e telefone inválido. Erros mais raros, como um JSON corrompido, ficam como melhoria. A ideia do projeto é mostrar o padrão de tratar no lugar certo, que você estende conforme a necessidade real.
Fontes
Seu progresso fica salvo neste aparelho. Assinantes sincronizam entre os aparelhos.