Curso de Python / Ler e gravar CSV com DictReader e DictWriter

CSV, de Comma Separated Values, armazena registros e campos como texto. Use o módulo csv para interpretar delimitadores e aspas e depois valide os dados: os valores lidos por DictReader neste exemplo são strings, inclusive os que representam números.

Interpretar campos com aspas e escolher a abertura do arquivo

Um campo pode conter uma vírgula sem representar uma coluna adicional: basta que o formato use as aspas apropriadas. Campos entre aspas também podem conter quebras de linha. split(",") não interpreta essas regras e pode separar incorretamente o conteúdo de um único campo. Use csv.reader para sequências de campos ou csv.DictReader para associá-los aos nomes do cabeçalho.

Abra os arquivos com newline="" para deixar o módulo csv administrar as quebras de linha. A codificação continua sendo uma escolha separada: utf-8-sig lê UTF-8 e remove uma assinatura BOM inicial quando ela está presente. BOM significa Byte Order Mark, uma marca que algumas ferramentas incluem no início do arquivo; sem o tratamento adequado, ela pode aparecer no primeiro nome de coluna.

CSV tem variações de delimitador, aspas e convenções entre programas. O exemplo usa vírgula como delimitador; um arquivo separado por ponto e vírgula exige configuração correspondente. O módulo resolve a estrutura, mas não decide se campos extras, cabeçalhos duplicados, notas vazias ou valores negativos são aceitáveis.

Gravar um nome com vírgula e calcular o total

Use uma pasta separada e descartável: o programa substitui expenses-demo.csv a cada execução. Salve o código completo em csv_demo.py, com UTF-8, e execute python csv_demo.py no terminal dessa pasta, com o ambiente virtual selecionado se houver. Em macOS ou Ubuntu sem ambiente virtual, use python3 csv_demo.py quando necessário. O módulo csv faz parte da biblioteca padrão.

import csv
from pathlib import Path

path = Path("expenses-demo.csv")
with path.open("w", encoding="utf-8", newline="") as f:
    writer = csv.DictWriter(f, fieldnames=["name", "amount"])
    writer.writeheader()
    writer.writerows([{ "name": "lunch, team", "amount": 8000}, {"name": "bus", "amount": 2500}])
total = 0
with path.open(encoding="utf-8-sig", newline="") as f:
    reader = csv.DictReader(f)
    if reader.fieldnames != ["name", "amount"]:
        raise ValueError("unexpected header")
    for row in reader:
        total += int(row["amount"])
        print(row["name"])
print("total:", total)

O resultado esperado é:

lunch, team
bus
total: 10500

DictWriter usa os nomes name e amount para gravar o cabeçalho e os registros. O nome lunch, team, correspondente a um almoço em equipe, contém uma vírgula e é gravado como um campo entre aspas. Ao ler, DictReader recupera esse nome inteiro, sem as aspas que serviam para delimitar o campo.

O leitor exige o cabeçalho exatamente na ordem ["name", "amount"]. Depois, int(row["amount"]) converte cada valor e o adiciona ao acumulador. Os dois valores produzem 10500. A coluna de nomes é exibida separadamente; a vírgula visível no primeiro nome não significa que o registro passou a ter três colunas.

Abra o CSV num editor de texto para observar as aspas e depois leia-o com um leitor CSV para verificar os valores. A simples aparência da saída do terminal não comprova a estrutura dos registros. A prática anterior de abertura de texto, codificação e modos de escrita ajuda a distinguir o conteúdo em disco da forma como ele é apresentado.

Definir validação de números, colunas e posições

O programa trata o arquivo criado por ele próprio. Ele não é um importador completo para qualquer CSV externo: cabeçalhos iguais não garantem que cada registro tenha o número certo de campos. Por padrão, campos excedentes de um DictReader podem aparecer sob a chave None; campos ausentes podem receber o valor None. Para aceitar dados externos, verifique a estrutura de cada registro antes de converter valores.

Uma string vazia, "8,000" ou um valor com símbolo de moeda não é aceito diretamente por int(). Não apague indiscriminadamente caracteres para forçar uma conversão: defina o formato permitido e registre os valores rejeitados. Além disso, converter com sucesso não valida regras de negócio; "-1" é convertido em inteiro negativo, mesmo que a aplicação não devesse aceitá-lo.

Erros de conversão interrompem este programa, pois não há tratamento de rejeitados no laço. Se a falha ocorrer num registro posterior, nomes dos registros anteriores já podem ter sido impressos, mas a linha final de total não será alcançada. Para produzir um relatório parcial confiável, seria necessário definir explicitamente quais erros permitem continuar e como informar as rejeições.

enumerate(reader, start=2) pode identificar registros lógicos após o cabeçalho. Quando um campo contém uma quebra de linha, essa numeração não corresponde às linhas físicas do arquivo. reader.line_num indica o número de linhas físicas já consumidas pelo leitor, e não simplesmente um número de registro. Informe qual posição está sendo usada no diagnóstico.

Verificar releitura, cabeçalho e exportações para planilhas

  1. Execute o programa e leia novamente o arquivo com csv.reader. Confirme que cada registro tem dois campos e que o primeiro nome é exatamente lunch, team. Abra o texto bruto e confira que o nome com vírgula está entre aspas.
  2. Para testar somente um cabeçalho inesperado, substitua temporariamente writer.writeheader() por writer.writerow({"name": "name", "amount": "cost"}). Isso mantém as chaves usadas na escrita dos dados, mas grava name,cost no cabeçalho. A leitura deve parar com ValueError: unexpected header, sem imprimir nomes nem total.
  3. Restaure writer.writeheader() e execute duas vezes. A saída continua igual e o arquivo contém um cabeçalho e dois registros, porque w substitui o conteúdo em cada execução.
  4. Para observar um campo multilinha, troque somente "lunch, team" por "lunch,\nteam" no registro escrito. O nome continua sendo um campo, embora a impressão ocupe duas linhas e a contagem de linhas físicas aumente. Restaure o texto original para recuperar a saída inicial.

Editar apenas o arquivo CSV e depois executar este programa inteiro não testa uma entrada externa alterada: a parte de escrita recria o arquivo antes da leitura. Para testar arquivos recebidos de fora, separe deliberadamente a rotina de leitura da criação dos dados e mantenha uma cópia dos arquivos de teste.

Ao compartilhar um CSV com uma planilha, considere que a aplicação pode interpretar determinados textos externos, como os que começam por =, como fórmulas. Colocar o campo entre aspas CSV protege a separação dos campos, mas não determina o tratamento da célula pela planilha. Defina uma política de exportação compatível com o programa destinatário. O módulo csv e suas classes de leitura e escrita cuida do formato; a referência de UTF-8 com assinatura explica o tratamento da marca inicial, sem substituir a validação dos valores.