Trilha de aprendizado · Nível 6 · Tutorial 7

Ler e gravar dados tabulares em CSV

Importar e exportar registros tabulares com o módulo csv, respeitando delimitadores, cabeçalhos e conversões de campos e identificando registros inválidos.

  • Nível: Intermediário
  • Duração: 25 min
  • 8 passos
Ler e gravar dados tabulares em CSV

O que você vai percorrer

  1. Entender a estrutura de um CSV Reconheça como cabeçalhos, registros, delimitadores, aspas e quebras de linha representam uma tabela em texto. 2 min
  2. Abrir o arquivo e ler com DictReader Configure a abertura de um arquivo CSV separado por ponto e vírgula e leia cada registro como um dicionário associado ao cabeçalho. 3 min
  3. Validar o cabeçalho antes dos registros Verifique as colunas declaradas pelo arquivo antes de percorrer e processar seus registros. 3 min
  4. Detectar registros incompletos ou excedentes Identifique campos vazios, ausentes e excedentes nos dicionários produzidos por DictReader e diferencie o número do registro da linha física consumida. 3 min
  5. Converter campos e validar os valores Converta os textos lidos do CSV para os valores esperados pela aplicação e rejeite registros inválidos com diagnósticos específicos. 3 min
  6. Exportar com colunas explícitas Use DictWriter para definir a ordem das colunas, gravar registros validados e conferir os dados por meio de uma nova leitura. 3 min
  7. Distinguir as causas de falha Classifique falhas de formato, conteúdo, codificação e acesso para interromper o fluxo no estágio correto. 3 min
  8. Aplicar o fluxo completo e conferir o resultado Execute o fluxo completo de importação, validação, exportação e releitura; depois provoque uma falha de conteúdo e confirme que a saída não é criada. 4 min

O que você vai aprender

  • Ler registros com DictReader e gravá-los com DictWriter usando colunas explícitas.
  • Configurar delimitador, encoding e newline conforme o arquivo esperado.
  • Converter campos textuais para os tipos necessários e validar cabeçalhos e registros.
  • Preservar campos que contêm delimitadores, aspas ou quebras de linha sem recorrer a split.
  • Distinguir falhas de leitura do CSV, inconsistências dos registros e problemas de acesso ao arquivo.

Antes de começar

  • Ler e gravar arquivos de texto com with
  • Validar entradas e sinalizar falhas com raise
  • Consultar e atualizar coleções aninhadas
  • Percorrer sequências com enumerate e zip

Passo 1 de 8

Entender a estrutura de um CSV

Reconheça como cabeçalhos, registros, delimitadores, aspas e quebras de linha representam uma tabela em texto.

Uma tabela representada como texto

CSV é um formato textual para representar dados tabulares. O primeiro registro costuma ser o cabeçalho, e os seguintes representam os dados. Cada registro contém campos separados por um delimitador.

O próprio CSV não define tipos: valores como 2 e 15 são texto no arquivo, mesmo que a aplicação decida convertê-los em números depois.

Correspondência por cores entre as células de uma tabela e os campos de um CSV separado por ponto e vírgula.

Cada célula corresponde a um campo; cada registro lógico corresponde a uma linha da tabela.

O delimitador depende do arquivo

O delimitador é uma convenção escolhida para aquele arquivo. Alguns CSVs usam vírgula; outros, ponto e vírgula. A extensão .csv não revela qual separador foi adotado.

Exemplo

Exemplo com ponto e vírgula

produto;quantidade;observacao
Caneca;2;Azul
Caderno;1;Sem pauta

Aqui há três campos em cada registro. Trocar mentalmente o ponto e vírgula por uma vírgula mudaria a interpretação da estrutura.

Aspas protegem campos complexos

Exemplo

Delimitadores, aspas e quebras internas

produto;quantidade;observacao
Caneca;2;"Modelo grande; azul"
Caderno;1;"Ela escreveu ""urgente"" na capa"
Cartaz;1;"Primeira linha
segunda linha"

As aspas delimitam um campo que contém o próprio separador ou uma quebra de linha. Na convenção padrão, uma aspa interna é representada por duas aspas ("").

Por isso, o registro de Cartaz ocupa duas linhas físicas do texto, mas continua sendo um único registro lógico com três campos. Usar split(";") separaria incorretamente o ponto e vírgula de "Modelo grande; azul"; dividir por linhas também quebraria o registro de Cartaz. O módulo csv é responsável por interpretar essas regras.

Confira a interpretação

Considere o registro Caneca;2;"Modelo grande; azul". Qual interpretação está correta?

Passo 2 de 8

Abrir o arquivo e ler com DictReader

Configure a abertura de um arquivo CSV separado por ponto e vírgula e leia cada registro como um dicionário associado ao cabeçalho.

Prepare o arquivo de entrada

Um cadastro separado por ponto e vírgula

O módulo csv faz parte da biblioteca padrão do Python, portanto não exige instalação. Para acompanhar o exemplo, crie no seu computador um arquivo chamado cadastro.csv com o conteúdo abaixo. O delimitador deste arquivo é o ponto e vírgula (;).

Conteúdo completo de cadastro.csv

Salve exatamente este conteúdo em UTF-8. As aspas permitem que o ponto e vírgula da primeira observação pertença ao próprio campo.

csv
produto;quantidade;observacao
Caderno;2;"Capa azul; 96 folhas"
Caneta;5;Ponta fina
Mochila;1;Uso diário

Dica

A extensão não escolhe o delimitador

Um arquivo com extensão .csv pode usar vírgula, ponto e vírgula ou outra convenção. Configure delimiter de acordo com o arquivo recebido.

Abra e configure o DictReader

Leitura em modo texto

Abra o arquivo com o encoding correspondente à sua origem. Neste exemplo, ele foi salvo em UTF-8. O argumento newline="" deixa o tratamento das quebras de linha do CSV sob responsabilidade do módulo csv, inclusive quando elas aparecem dentro de campos entre aspas.

Leitura do cadastro

O for permanece dentro do bloco with, enquanto o arquivo está aberto.

python
from pathlib import Path
import csv

caminho = Path("cadastro.csv")

with caminho.open(
    mode="r",
    encoding="utf-8",
    newline="",
) as arquivo:
    reader = csv.DictReader(arquivo, delimiter=";")

    for registro in reader:
        print(registro["produto"])
        print(registro["quantidade"])
        print(registro["observacao"])

Dica

Encoding depende da origem

UTF-8 é a escolha deste exemplo, não uma propriedade automática de todo CSV. Ao receber outro arquivo, descubra qual codificação foi usada e informe-a explicitamente.

Do cabeçalho aos dicionários

Cada registro ganha chaves

Como fieldnames não foi informado, o DictReader usa o primeiro registro como cabeçalho. Assim, produto, quantidade e observacao tornam-se as chaves dos dicionários produzidos. Nesta etapa, todos os valores lidos são textos — inclusive "2".

Associação feita pelo DictReader

No primeiro registro de dados, cada nome do cabeçalho é associado ao campo que ocupa a mesma coluna.

Diagrama que liga as colunas produto, quantidade e observacao aos pares correspondentes de um dicionário do primeiro registro.

O primeiro registro de dados resulta em um dicionário com produto igual a Caderno, quantidade igual ao texto 2 e observacao igual a Capa azul; 96 folhas.

Exemplo

Primeiro dicionário produzido

Para o primeiro registro de dados, o resultado corresponde a:

{'produto': 'Caderno', 'quantidade': '2', 'observacao': 'Capa azul; 96 folhas'}

O ponto e vírgula dentro da observação é preservado como parte do campo.

Confira a configuração e o resultado

Complete a abertura

Para abrir o arquivo UTF-8 corretamente para o módulo csv, complete o argumento:

with caminho.open(mode="r", encoding="utf-8", newline=____) as arquivo:

Reconheça o registro

Qual dicionário o DictReader produz para a linha Caneta;5;Ponta fina?

Passo 3 de 8

Validar o cabeçalho antes dos registros

Verifique as colunas declaradas pelo arquivo antes de percorrer e processar seus registros.

O cabeçalho é o contrato de entrada

Consulte antes de percorrer

Ao criar um DictReader sem fornecer fieldnames, o primeiro registro do CSV é usado como cabeçalho. Consulte reader.fieldnames antes do for e compare os nomes recebidos com as colunas exigidas pela aplicação: produto, quantidade e observacao.

A ordem pode variar, pois cada valor será acessado pela chave correspondente. Assim, observacao;produto;quantidade é válido. Já nomes ausentes, inesperados, vazios ou repetidos devem interromper a importação antes do processamento dos registros.

Se o arquivo estiver vazio, fieldnames será None. Se a primeira linha contiver dados em vez de um cabeçalho, o DictReader a interpretará como cabeçalho; a comparação com as colunas esperadas normalmente revelará a incompatibilidade.

Quatro resultados possíveis

Comparação entre o cabeçalho esperado e quatro casos: mesmas colunas em outra ordem, coluna quantidade ausente, coluna preco inesperada e nome produto repetido.

A ordem diferente é aceita; ausência, excesso, vazio ou repetição de nomes tornam o cabeçalho inválido.

Validar sem perder duplicatas

Uma validação com diagnósticos específicos

Detecte nomes vazios e repetidos antes de converter o cabeçalho em conjunto. Um conjunto eliminaria as repetições e poderia esconder justamente o defeito que precisa ser informado.

python
import csv

COLUNAS_ESPERADAS = {"produto", "quantidade", "observacao"}


def validar_cabecalho(reader):
    cabecalho = reader.fieldnames

    if cabecalho is None:
        raise ValueError("arquivo vazio: cabeçalho ausente")

    posicoes_vazias = [
        indice
        for indice, nome in enumerate(cabecalho, start=1)
        if nome.strip() == ""
    ]
    if posicoes_vazias:
        raise ValueError(
            f"cabeçalho com nome vazio nas posições {posicoes_vazias}"
        )

    repetidos = sorted({
        nome for nome in cabecalho if cabecalho.count(nome) > 1
    })
    if repetidos:
        raise ValueError(
            f"cabeçalho com nomes repetidos: {repetidos}"
        )

    recebidas = set(cabecalho)
    ausentes = sorted(COLUNAS_ESPERADAS - recebidas)
    inesperadas = sorted(recebidas - COLUNAS_ESPERADAS)

    if ausentes or inesperadas:
        detalhes = []
        if ausentes:
            detalhes.append(f"colunas ausentes: {ausentes}")
        if inesperadas:
            detalhes.append(f"colunas inesperadas: {inesperadas}")
        raise ValueError("cabeçalho incompatível: " + "; ".join(detalhes))


with open("cadastro.csv", encoding="utf-8", newline="") as arquivo:
    reader = csv.DictReader(arquivo, delimiter=";")
    validar_cabecalho(reader)

    for registro in reader:
        print(registro)

Atenção

Duplicatas podem ocultar valores

Em um cabeçalho como produto;produto;observacao, as duas primeiras colunas produzem a mesma chave. Ao montar o dicionário, um valor pode substituir o outro. Por isso, procure nomes repetidos antes de comparar apenas os conjuntos de colunas e antes de acessar qualquer registro.

Diagnostique cada cabeçalho

Relacione cabeçalho e diagnóstico

Considere que o delimitador já foi configurado como ponto e vírgula. Associe cada cabeçalho ao diagnóstico correto.

Toque em um item e depois no par correspondente.

Passo 4 de 8

Detectar registros incompletos ou excedentes

Identifique campos vazios, ausentes e excedentes nos dicionários produzidos por DictReader e diferencie o número do registro da linha física consumida.

Três situações diferentes

Vazio, ausente ou excedente?

Depois de validar o cabeçalho, verifique a estrutura de cada registro antes de acessar ou converter seus valores.

Com as colunas produto, quantidade e observacao, o DictReader representa situações diferentes assim:

  • Campo presente, mas vazio: valor "".
  • Campo estruturalmente ausente: valor None na coluna sem correspondência.
  • Campo excedente: chave None associada a uma lista com os valores que sobraram.

A chave None indica excesso de campos; um valor None em uma coluna esperada indica falta de campo.

Anatomia das inconsistências

Comparação visual entre uma célula vazia, uma célula ausente e uma célula que excede as três colunas esperadas.

A célula vazia ainda ocupa sua coluna; a ausente não foi fornecida; a excedente ultrapassa a estrutura esperada.

Como o DictReader representa cada caso

Dicionários resultantes

Considere que o cabeçalho válido contém três colunas. Estes são os resultados conceituais produzidos para cada registro:

python
# produto;quantidade;observacao

# Caderno;5;
{
    "produto": "Caderno",
    "quantidade": "5",
    "observacao": "",
}

# Lápis;10
{
    "produto": "Lápis",
    "quantidade": "10",
    "observacao": None,
}

# Borracha;3;Branca;Escolar
{
    "produto": "Borracha",
    "quantidade": "3",
    "observacao": "Branca",
    None: ["Escolar"],
}

Dica

A ordem da validação importa

Primeiro procure campos excedentes e ausentes. Somente depois acesse os conteúdos para aplicar conversões ou regras de valor. Uma string vazia pode ser permitida pela aplicação; None indica que o campo nem sequer estava presente no registro.

Validar e localizar o registro

Registro lógico não é linha física

Use enumerate(reader, start=1) para numerar os registros de dados sem contar o cabeçalho. Um registro continua recebendo um único número mesmo quando um campo entre aspas ocupa várias linhas físicas.

Depois que o DictReader entrega um registro com sucesso, reader.line_num indica a última linha física consumida naquele momento. Esse valor oferece contexto, mas não substitui o número do registro lógico.

Verificação estrutural antes dos valores

Este trecho pressupõe que reader já foi configurado e que seu cabeçalho já foi validado:

python
colunas = ["produto", "quantidade", "observacao"]

for numero_registro, registro in enumerate(reader, start=1):
    linha_fisica_final = reader.line_num

    if None in registro:
        excedentes = registro[None]
        raise ValueError(
            f"Registro {numero_registro} tem campos excedentes: "
            f"{excedentes!r}. Última linha física consumida: "
            f"{linha_fisica_final}."
        )

    ausentes = [
        coluna
        for coluna in colunas
        if registro[coluna] is None
    ]

    if ausentes:
        raise ValueError(
            f"Registro {numero_registro} não contém os campos: "
            f"{ausentes!r}. Última linha física consumida: "
            f"{linha_fisica_final}."
        )

    # Somente agora é seguro acessar e validar os conteúdos.

Atenção

O que line_num informa

reader.line_num indica até qual linha física a leitura avançou. Se um registro ocupar várias linhas, ele normalmente será diferente do número obtido por enumerate. Não descreva esse valor como se fosse sempre a linha exata onde o problema começou.

Confira as representações

Associe cada estrutura ao seu significado

Relacione a representação produzida pelo DictReader com a situação do registro.

Toque em um item e depois no par correspondente.

Registro lógico e linhas físicas

Considere este conteúdo:

produto;quantidade;observacao
Caneca;2;Azul
Agenda;1;"Capa dura
com elástico"

Ao entregar o registro da Agenda, quais valores correspondem a numero_registro e reader.line_num?

Passo 5 de 8

Converter campos e validar os valores

Converta os textos lidos do CSV para os valores esperados pela aplicação e rejeite registros inválidos com diagnósticos específicos.

Do texto ao valor da aplicação

O CSV entrega representações textuais

Na configuração adotada, os campos presentes chegam pelo DictReader como strings. Assim, "12" não é o inteiro 12: a aplicação precisa fazer a conversão explicitamente.

Para o cadastro deste tutorial:

  • produto permanece texto e não pode estar vazio nem conter apenas espaços;
  • quantidade deve ser convertida com int e resultar em um inteiro maior ou igual a zero;
  • observacao permanece texto e pode ser "".

Essa validação ocorre depois de confirmar o cabeçalho e a quantidade de campos do registro.

Conversão seletiva dos campos

Diagrama de um registro com três campos textuais entrando em uma validação; somente o campo central é convertido para inteiro.

A aplicação converte apenas quantidade. produto e observacao continuam sendo textos.

Converter sem perder o contexto

Função de conversão e validação

Esta função pressupõe que o cabeçalho e a estrutura do registro já foram validados. Ela cria um novo dicionário, em vez de misturar os textos de entrada com os valores aprovados.

python
def converter_registro(registro, numero_registro):
    produto = registro["produto"]
    quantidade_texto = registro["quantidade"]
    observacao = registro["observacao"]

    if not produto.strip():
        raise ValueError(
            f"Registro {numero_registro}, campo produto: "
            "deve conter algum caractere diferente de espaço"
        )

    try:
        quantidade = int(quantidade_texto)
    except ValueError:
        raise ValueError(
            f"Registro {numero_registro}, campo quantidade: "
            f"{quantidade_texto!r} não representa um número inteiro"
        ) from None

    if quantidade < 0:
        raise ValueError(
            f"Registro {numero_registro}, campo quantidade: "
            "deve ser maior ou igual a zero"
        )

    return {
        "produto": produto,
        "quantidade": quantidade,
        "observacao": observacao,
    }

Dica

Conversão e regra são verificações diferentes

Se int(quantidade_texto) falhar, o texto não representa um inteiro. Se a conversão funcionar, mas o resultado for negativo, houve uma violação da regra da aplicação. Separar os casos produz diagnósticos mais úteis.

Preservar o que não deve ser normalizado

Exemplo

Observações válidas

Os três valores de observacao abaixo são válidos e devem ser preservados exatamente como foram interpretados pelo módulo csv:

  • "" — observação vazia;
  • Frágil; manter na vertical — contém o delimitador do arquivo;
  • Cliente disse "entregar amanhã" — contém aspas;
  • Primeira linha\nSegunda linha — contém uma quebra de linha interna.

Não aplique strip, split nem substituições indiscriminadas à observação. Esses procedimentos poderiam apagar espaços, aspas ou quebras de linha que fazem parte do conteúdo.

Validar tudo antes de exportar

Converta os registros em ordem e acumule apenas os novos dicionários validados. Neste fluxo, a política é interromper a importação no primeiro registro inválido. A exportação só deve começar depois que todos os registros de entrada forem aprovados; assim, uma falha de validação não inicia a criação da saída.

Diagnostique os registros

Conversão, regra ou campo obrigatório?

Analise estes registros como se a estrutura de todos já tivesse sido aprovada:

  1. {"produto": "Caderno", "quantidade": "4", "observacao": "Capa azul"}
  2. {"produto": "Caneta", "quantidade": "duas", "observacao": ""}
  3. {"produto": "Borracha", "quantidade": "-1", "observacao": "Estoque ajustado"}
  4. {"produto": " ", "quantidade": "3", "observacao": ""}

Indique quais seriam válidos se analisados isoladamente, classifique o motivo de cada rejeição e informe o tipo de quantidade após uma conversão bem-sucedida. Depois, considerando a política de parar na primeira falha, diga onde a importação seria interrompida e se a exportação poderia começar.

Escreva pelo menos 80 caracteres (0/80).

Passo 6 de 8

Exportar com colunas explícitas

Use DictWriter para definir a ordem das colunas, gravar registros validados e conferir os dados por meio de uma nova leitura.

As colunas da saída

A ordem vem de fieldnames

Na exportação, fieldnames define quais colunas serão gravadas e em que ordem. Essa ordem não depende da disposição das chaves em cada dicionário.

Abra um caminho de saída diferente da entrada, em modo texto, com o encoding escolhido e newline="". O delimitador também deve ser explícito e compatível com o arquivo que você deseja produzir.

Dos dicionários para a tabela

O DictWriter consulta cada valor pela chave e o posiciona na coluna indicada por fieldnames.

Diagrama de dicionários com chaves em ordens variadas sendo convertidos em uma tabela com três colunas de ordem fixa.

Mesmo que as chaves apareçam em ordens diferentes, a saída segue a sequência definida em fieldnames.

Gravar cabeçalho e registros

Exportação com DictWriter

Este exemplo usa registros que já passaram pelas validações de estrutura e valores.

python
import csv
from pathlib import Path

colunas = ["produto", "quantidade", "observacao"]

registros = [
    {
        "observacao": "pacote; 500 g",
        "quantidade": 12,
        "produto": "Café",
    },
    {
        "produto": "Caneca",
        "observacao": 'Ela disse "frágil".',
        "quantidade": 3,
    },
    {
        "quantidade": 5,
        "observacao": "capa\nazul",
        "produto": "Caderno",
    },
]

caminho_saida = Path("cadastro_exportado.csv")

with caminho_saida.open(
    "w", encoding="utf-8", newline=""
) as arquivo:
    writer = csv.DictWriter(
        arquivo,
        fieldnames=colunas,
        delimiter=";",
    )
    writer.writeheader()
    writer.writerows(registros)

print(f"Arquivo gravado: {caminho_saida}")

O escritor protege os campos

writeheader() grava o cabeçalho na ordem de fieldnames. Depois, writerows() grava todos os registros validados.

O escritor acrescenta as aspas necessárias para preservar o ponto e vírgula, as aspas internas e a quebra de linha dentro de observacao. Não monte as linhas com join nem tente escapar esses caracteres manualmente.

Exemplo

Representação aproximada do arquivo

O conteúdo será equivalente a:

produto;quantidade;observacao
Café;12;"pacote; 500 g"
Caneca;3;"Ela disse ""frágil""."
Caderno;5;"capa
azul"

O último registro ocupa duas linhas físicas porque a quebra de linha faz parte do campo observacao.

Atenção

Não sobrescreva a entrada

O modo "w" pode truncar um arquivo existente assim que ele é aberto. Use um caminho de saída diferente do caminho de entrada. A escrita deve começar somente depois que todos os registros tiverem sido validados.

Conferir a ida e volta

Releia e reconverta

CSV armazena representações textuais. Por isso, ao reler a saída com DictReader, quantidade volta como texto. Reaplique int antes de comparar o registro relido com o dicionário validado.

A verificação deve comparar os conteúdos dos campos, não os bytes dos arquivos: detalhes como terminadores de linha podem variar sem alterar os dados tabulares.

Releitura da saída

Execute este trecho depois do código de exportação.

python
registros_relidos = []

with caminho_saida.open(
    "r", encoding="utf-8", newline=""
) as arquivo:
    reader = csv.DictReader(arquivo, delimiter=";")

    for registro in reader:
        registros_relidos.append(
            {
                "produto": registro["produto"],
                "quantidade": int(registro["quantidade"]),
                "observacao": registro["observacao"],
            }
        )

print(registros_relidos == registros)
# Resultado esperado: True

O que volta na leitura?

Antes de aplicar int, como o primeiro registro é entregue pelo DictReader?

Sequência da exportação

Coloque as operações na ordem

Ordene as etapas para exportar os registros já validados.

  1. Chamar writerows() com os registros previamente validados.
  2. Criar o DictWriter com fieldnames e delimiter explícitos.
  3. Chamar writeheader() para gravar o cabeçalho.
  4. Abrir o arquivo de saída com encoding explícito e newline="".

Passo 7 de 8

Distinguir as causas de falha

Classifique falhas de formato, conteúdo, codificação e acesso para interromper o fluxo no estágio correto.

O que strict=True detecta

Formato não é o mesmo que validade

Ao criar um DictReader, use strict=True para tornar mais rigorosa a interpretação do formato. Um campo entre aspas que chega ao fim do arquivo sem fechar as aspas, por exemplo, pode provocar csv.Error durante o percurso dos registros.

Isso não torna o arquivo válido para sua aplicação. Cabeçalho incorreto ou repetido, campos a mais ou a menos e valores como quantidade="dez" ainda exigem as validações explícitas dos passos anteriores.

Quatro categorias, tratamentos diferentes

A etapa em que a falha ocorre ajuda a classificá-la: interpretação do CSV, validação dos dados, conversão de caracteres ou acesso ao arquivo.

Diagrama com um arquivo CSV seguindo por quatro caminhos: erro de formato, valor inválido, codificação incompatível e falha de acesso.

Não trate todas as falhas como se fossem um único “erro de CSV”.

O erro pode surgir durante o for

Proteja toda a operação de leitura

Criar o DictReader não consome todos os registros. Por isso, o try precisa envolver também o for: uma falha de formato ou codificação pode aparecer somente quando o trecho problemático for lido.

No fluxo abaixo, a exportação começa apenas no else, depois que a importação inteira termina com sucesso.

Separação entre importação e exportação

O exemplo mantém tratamentos específicos e não inicia a escrita quando a entrada falha.

python
import csv

COLUNAS = ["produto", "quantidade", "observacao"]


def importar(caminho):
    registros = []

    with open(caminho, "r", encoding="utf-8", newline="") as arquivo:
        reader = csv.DictReader(
            arquivo,
            delimiter=";",
            strict=True,
        )

        cabecalho = reader.fieldnames
        if cabecalho is None:
            raise ValueError("cabeçalho ausente")
        if "" in cabecalho:
            raise ValueError("nome de coluna vazio")
        if len(cabecalho) != len(set(cabecalho)):
            raise ValueError("nome de coluna repetido")
        if set(cabecalho) != set(COLUNAS):
            raise ValueError("colunas incompatíveis")

        for numero, registro in enumerate(reader, start=1):
            if None in registro:
                raise ValueError(f"registro {numero}: campos excedentes")
            if any(valor is None for valor in registro.values()):
                raise ValueError(f"registro {numero}: campos ausentes")

            try:
                quantidade = int(registro["quantidade"])
            except ValueError as erro:
                raise ValueError(
                    f"registro {numero}, campo quantidade: inteiro inválido"
                ) from erro

            if quantidade < 0:
                raise ValueError(
                    f"registro {numero}, campo quantidade: valor negativo"
                )

            registros.append({
                "produto": registro["produto"],
                "quantidade": quantidade,
                "observacao": registro["observacao"],
            })

    return registros


def exportar(caminho, registros):
    with open(caminho, "w", encoding="utf-8", newline="") as arquivo:
        writer = csv.DictWriter(
            arquivo,
            fieldnames=COLUNAS,
            delimiter=";",
        )
        writer.writeheader()
        writer.writerows(registros)


entrada = "cadastro.csv"
saida = "cadastro_validado.csv"

try:
    dados = importar(entrada)
except csv.Error as erro:
    print(f"Formato CSV inválido em {entrada}: {erro}")
except UnicodeDecodeError as erro:
    print(f"Não foi possível decodificar {entrada}: {erro}")
except OSError as erro:
    print(f"Falha de acesso a {entrada}: {erro}")
except ValueError as erro:
    print(f"Conteúdo inválido em {entrada}: {erro}")
else:
    try:
        exportar(saida, dados)
    except UnicodeEncodeError as erro:
        print(f"Não foi possível codificar {saida}: {erro}")
    except OSError as erro:
        print(f"Falha ao gravar {saida}: {erro}")
    else:
        print(f"Dados exportados para {saida}")

Diagnósticos sem falsas garantias

Informe o contexto disponível

Um diagnóstico útil identifica o caminho, a categoria e, quando conhecido, o registro ou campo afetado. reader.line_num pode indicar a última linha física consumida após um registro lido com sucesso, mas não deve ser apresentado como a posição exata de todo csv.Error.

As categorias principais são: csv.Error para certas falhas de interpretação; ValueError para conversões e regras da aplicação; UnicodeDecodeError ou UnicodeEncodeError para codificação; e OSError para acesso e gravação.

Atenção

Uma escrita que falha não é desfeita

Se a importação falhar, não inicie a exportação. Se a escrita já começou e depois ocorrer uma falha, o arquivo de saída pode ter sido criado, truncado ou parcialmente preenchido. Fechar o arquivo com with libera o recurso, mas não desfaz bytes ou caracteres já gravados.

Associe a falha ao tratamento

Classificação de falhas

Associe cada cenário à categoria ou ação adequada.

Toque em um item e depois no par correspondente.

Passo 8 de 8

Aplicar o fluxo completo e conferir o resultado

Execute o fluxo completo de importação, validação, exportação e releitura; depois provoque uma falha de conteúdo e confirme que a saída não é criada.

Prepare o caso válido

Um fluxo com duas fases

Você reunirá todo o processo em um script local. Primeiro, ele importa e valida todos os registros. Somente se essa fase terminar sem falhas, o programa inicia a exportação. Por fim, ele relê a saída e compara os valores convertidos.

Crie uma pasta para a prática. Dentro dela, crie os arquivos cadastro_entrada.csv e processar_csv.py com os conteúdos apresentados a seguir.

Visão do fluxo completo

A sequência protege a exportação: configuração da leitura → validação do cabeçalho → validação estrutural → conversão e regras → escrita → releitura e comparação.

Diagrama de um documento CSV passando por quatro pontos de verificação, sendo gravado em outro documento e relido para comparação.

A saída só entra no fluxo depois que todos os registros da entrada passam pelas validações.

cadastro_entrada.csv

Salve exatamente este conteúdo em UTF-8. As aspas protegem o ponto e vírgula, as aspas internas e a quebra de linha que pertencem aos campos.

csv
produto;quantidade;observacao
Caderno;12;"Capa azul; 96 folhas"
Caneta;3;"Modelo ""premium"""
Agenda;1;"Entrega em duas
etapas"

Execute o script completo

Importar antes de exportar

O script mantém as responsabilidades separadas: csv interpreta e escreve o formato; as funções da aplicação verificam colunas, estrutura, tipos e regras. A chamada de exportar_csv está no bloco else da importação, portanto não ocorre quando a entrada falha.

processar_csv.py

Salve o código na mesma pasta do CSV de entrada e execute python processar_csv.py no terminal dessa pasta.

python
import csv

COLUNAS = ["produto", "quantidade", "observacao"]
ENTRADA = "cadastro_entrada.csv"
SAIDA = "cadastro_saida.csv"


def validar_cabecalho(nomes):
    if nomes is None:
        raise ValueError("arquivo vazio ou cabeçalho ausente")

    if any(nome == "" for nome in nomes):
        raise ValueError("o cabeçalho contém um nome vazio")

    repetidos = []
    for nome in nomes:
        if nomes.count(nome) > 1 and nome not in repetidos:
            repetidos.append(nome)

    if repetidos:
        raise ValueError(
            f"colunas repetidas no cabeçalho: {repetidos}"
        )

    esperadas = set(COLUNAS)
    recebidas = set(nomes)
    faltantes = sorted(esperadas - recebidas)
    inesperadas = sorted(recebidas - esperadas)

    if faltantes:
        raise ValueError(f"colunas obrigatórias ausentes: {faltantes}")
    if inesperadas:
        raise ValueError(f"colunas inesperadas: {inesperadas}")


def validar_e_converter(linha, numero_registro, linha_fisica):
    extras = linha.get(None)
    if extras is not None:
        raise ValueError(
            f"registro {numero_registro}: campos excedentes {extras}; "
            f"última linha física consumida: {linha_fisica}"
        )

    ausentes = [
        coluna for coluna in COLUNAS if linha[coluna] is None
    ]
    if ausentes:
        raise ValueError(
            f"registro {numero_registro}: campos ausentes {ausentes}; "
            f"última linha física consumida: {linha_fisica}"
        )

    produto = linha["produto"]
    observacao = linha["observacao"]

    if produto.strip() == "":
        raise ValueError(
            f"registro {numero_registro}, campo produto: valor obrigatório"
        )

    try:
        quantidade = int(linha["quantidade"])
    except ValueError:
        raise ValueError(
            f"registro {numero_registro}, campo quantidade: "
            f"{linha['quantidade']!r} não é um inteiro"
        )

    if quantidade < 0:
        raise ValueError(
            f"registro {numero_registro}, campo quantidade: "
            "deve ser maior ou igual a zero"
        )

    return {
        "produto": produto,
        "quantidade": quantidade,
        "observacao": observacao,
    }


def importar_csv(caminho):
    registros = []

    with open(caminho, "r", encoding="utf-8", newline="") as arquivo:
        leitor = csv.DictReader(
            arquivo,
            delimiter=";",
            strict=True,
        )
        validar_cabecalho(leitor.fieldnames)

        for numero, linha in enumerate(leitor, start=1):
            registro = validar_e_converter(
                linha,
                numero_registro=numero,
                linha_fisica=leitor.line_num,
            )
            registros.append(registro)

    return registros


def exportar_csv(caminho, registros):
    with open(caminho, "w", encoding="utf-8", newline="") as arquivo:
        escritor = csv.DictWriter(
            arquivo,
            fieldnames=COLUNAS,
            delimiter=";",
        )
        escritor.writeheader()
        escritor.writerows(registros)


def main():
    try:
        registros = importar_csv(ENTRADA)
    except csv.Error as erro:
        print(f"Falha de formato CSV na importação: {erro}")
    except ValueError as erro:
        print(f"Falha de conteúdo na importação: {erro}")
    except UnicodeDecodeError as erro:
        print(f"Falha de codificação na importação: {erro}")
    except OSError as erro:
        print(f"Falha de acesso à entrada {ENTRADA!r}: {erro}")
    else:
        print(f"Importados: {registros}")

        try:
            exportar_csv(SAIDA, registros)
            relidos = importar_csv(SAIDA)

            if relidos != registros:
                raise ValueError("a releitura produziu valores diferentes")
        except csv.Error as erro:
            print(f"Falha de formato na exportação ou releitura: {erro}")
        except (UnicodeDecodeError, UnicodeEncodeError) as erro:
            print(f"Falha de codificação na exportação ou releitura: {erro}")
        except ValueError as erro:
            print(f"Falha na verificação da saída: {erro}")
        except OSError as erro:
            print(f"Falha de acesso à saída {SAIDA!r}: {erro}")
        else:
            print(f"Exportação concluída: {SAIDA}")
            print(f"Relidos: {relidos}")
            print(f"Ida e volta preservada: {relidos == registros}")


if __name__ == "__main__":
    main()

Dica

Não compare apenas o texto bruto

O escritor pode escolher uma representação textual válida sem reproduzir cada byte da entrada. A verificação relevante é reler o arquivo, reaplicar as conversões e comparar os conteúdos dos campos.

Confira a ida e volta

Resultado esperado do caso válido

A execução deve criar cadastro_saida.csv e terminar com Ida e volta preservada: True. Nos valores exibidos, confirme três detalhes:

  • quantidade aparece como inteiro porque importar_csv aplicou int tanto na entrada quanto na releitura;
  • o ponto e vírgula de Capa azul; 96 folhas continua dentro da observação;
  • as aspas de Modelo "premium" e a quebra de linha de Entrega em duas\netapas foram preservadas.

Abra também cadastro_saida.csv em um editor de texto. A coluna quantidade estará novamente representada como texto no arquivo; CSV não registra o tipo Python.

Trecho final esperado no terminal

A representação exata do caminho ou de eventuais mensagens do sistema pode variar, mas o caso válido deve terminar assim:

text
Exportação concluída: cadastro_saida.csv
Relidos: [{'produto': 'Caderno', 'quantidade': 12, 'observacao': 'Capa azul; 96 folhas'}, {'produto': 'Caneta', 'quantidade': 3, 'observacao': 'Modelo "premium"'}, {'produto': 'Agenda', 'quantidade': 1, 'observacao': 'Entrega em duas\netapas'}]
Ida e volta preservada: True

Relate sua verificação

O que você observou na execução válida? Informe se a saída foi criada, como os campos especiais ficaram após a releitura e quando quantidade voltou a ser um inteiro.

Escreva pelo menos 80 caracteres (0/80).

Provoque uma falha e conclua

Caso inválido

Primeiro, renomeie cadastro_saida.csv para cadastro_saida_valida.csv. Depois, substitua o conteúdo de cadastro_entrada.csv pelo caso abaixo e execute novamente. O segundo registro tem uma quantidade que não pode ser convertida. O resultado esperado é uma falha de conteúdo no registro 2; um novo cadastro_saida.csv não deve ser criado.

csv
produto;quantidade;observacao
Caderno;12;"Capa azul; 96 folhas"
Caneta;x;"Modelo ""premium"""
Agenda;1;"Entrega em duas
etapas"

Diagnostique a execução inválida

Relate o resultado real da segunda execução. Qual registro e campo falharam? A falha pertence ao formato CSV, ao conteúdo, à codificação ou ao acesso? Por que a exportação não começou e o que aconteceu com a entrada?

Escreva pelo menos 100 caracteres (0/100).

Resumo

Responsabilidades no fluxo CSV

Você integrou leitura, validação, conversão, escrita e verificação sem interpretar linhas com split nem montá-las com join.

  • O módulo csv reconhece registros, delimitadores, aspas e quebras de linha internas e produz uma representação CSV válida.
  • A aplicação define os nomes das colunas, detecta campos ausentes ou excedentes, converte tipos e aplica regras de validade.
  • A importação deve terminar com sucesso antes do início da exportação.
  • A releitura recupera textos; conversões como int precisam ser aplicadas novamente para reconstruir os valores usados pela aplicação.
  • Falhas de formato CSV, conteúdo, codificação e acesso exigem diagnósticos distintos.

Tutorial concluído

Parabéns! Você concluiu: Ler e gravar dados tabulares em CSV

Agora você consegue importar registros com DictReader, validar cabeçalhos, estrutura e valores, exportar com DictWriter e verificar a preservação dos dados por releitura.

Baixe o Aplicativo agora para ter acesso a + de 5000 cursos gratuitos, exercícios, certificado e muito conteúdo sem pagar nada!

  • Cursos online 100% gratuitos do início ao fim

    Milhares de cursos online em vídeo, ebooks e áudiobooks.

  • Mais de 60 mil exercícios gratuitos

    Para testar seus conhecimentos no decorrer dos cursos online

  • Certificado Digital gratuito válido em todo o Brasil

    Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail

Aplicativo Cursa na tela de ebook, na tela de curso em vídeo e na tela de exercícios do curso, mais o certificado de conclusão de curso