Usando arquivos CSV em Python

csv-python-1_abertura

Já há algum tempo, eu precisei que gerar uma arquivo CSV em um programa em Python e como desconhecia, na época, a existência de suporte nativo acabei criando meu próprio código para tal. Tempos depois, revisando rotinas de importação para banco de dados que me passaram, reparei que o autor implementara a rotina própria para carregar estes mesmo arquivos.

Bem, considerando que o módulo faz parte da biblioteca padrão e é de fácil utilização — tão fácil ao ponto de não fazer sentido escrever a própria rotina aliás — só posso considerar que isto só ocorre por puro desconhecimento de sua existência.

Então, é bom escrever algumas linhas sobre este assunto…

O arquivo CSV

TL;DR Um arquivo CSV (comma-separated values) contém dados tabulados com valores separados por vírgulas e quebras de linha para dividir os registros.

O uso de dados com valores separados por vírgulas¹ já era utilizado pelo compilador FORTRAN do IBM OS/360, em 1972, antes de ser oficialmente incorporado na linguagem em 1978 (a especificação FORTRAN 77). Tanto o nome “comma-separated value” quanto o acrônimo “CSV” apareceram pela primeira no vez no manual do Osborne 1 em 1983, onde foi detalhado a forma de criação do arquivo para seu uso no SuperCalc.

Basicamente um arquivo CSV contém informações na forma de tabela com os dados delimitados por vírgulas (ou algum outro tipo de caractere separador) e os registros separados por quebras de linha. Podem ser  facilmente criados (tanto de forma mecânica como também manual) e são largamente usados para intercâmbio de informações entre bancos de dados² e planilhas (e/ou vice-versa), manipulados diretamente através de ferramentas do UNIX (sort, tr, uniq etc) ou através de linguagens de programação.

Mas ainda hoje o CSV é mais para um “conceito” do que um formato de arquivos, apesar de tentativas de formalizá-lo como as da IETF (RFC 4180 e RFC 7111 ) e W3C, tanto que na minha cabeça o separador padrão de valores do CSV é o ponto e vírgula e não a vírgula… 🙂

Em Python o suporte para leitura e gravação de arquivos CSV é provido pelo módulo csv (dah!), que é parte da biblioteca padrão e foi introduzido na versão 2.3 da linguagem (PEP-305).

(¹) Nos mesmos moldes do comando “DATA” de diversos dialetos de BASIC.

(²) Um bom exemplo disto são os comandos “LOAD DATA …” e “SELECT … INTO OUTFILE …”  no MySQL para, respectivamente, importar e exportar dados de tabelas que permitem produzir arquivos CSV.

Lendo e escrevendo arquivos CSV

Primeiramente é bom gerar um arquivo CSV para usar nos testes…

$ cat students.csv
1,Agatha da Costa,Via Cauã Rezende,Paraíso,da Mata,São Paulo,29093-532
2,Benício das Neves,"Rua de da Paz, 86",Capitão Eduardo,Pereira de Rodrigues,Rio de Janeiro,83392-182
3,Júlia da Paz,"Praia Moraes, 25",Conjunto Taquaril,Jesus de Goiás,Goiás,15319-202
4,Dr. Pedro Lucas Peixoto,"Esplanada Davi Luiz Freitas, 4",Virgínia,Pires de Minas,Distrito Federal,24123-321
5,Sophia Araújo,"Vila Beatriz Ramos, 125",Chácara Leonina,Lima,Minas Gerais,32024-590
6,Esther Freitas,"Pátio Fogaça, 52",Braúnas,da Rocha do Campo,Amapá,43302-598
7,Camila Rocha,"Fazenda Santos, 8",Serra,Correia das Flores,Santa Catarina,76917-907
8,Ana Júlia Porto,"Lagoa Sarah Moraes, 58",Monte São José,Lopes,Minas Gerais,82636-495
9,Pietra Campos,"Favela Natália Caldeira, 88",Xodo-Marize,Farias,Paraná,33447-453
10,João Felipe Carvalho,"Lago Davi Sales, 60",Renascença,Cunha,Mato Grosso do Sul,56446-679

Ah sim, este arquivo foi criado pelo Faker, logo os valores dele podem não fazer lá muito sentido.

Lendo um arquivo CSV

Para carregá-lo é necessário utilizar a função reader() do módulo csv:

from csv import reader

open("students.csv", "r") as f:
    students = [record for record in reader(f)]

for student in students[1:]:
    print("{0:02d} : {1}".format(int(student[0]), student[1]))

O programa lê o conteúdo do arquivo CSV e armazena em ‘students’, depois escreve na tela os dois primeiros valores de cada registro…

$ python read_csv_file.py
01 : Agatha da Costa
02 : Benício das Neves
03 : Júlia da Paz
04 : Dr. Pedro Lucas Peixoto
05 : Sophia Araújo
06 : Esther Freitas
07 : Camila Rocha
08 : Ana Júlia Porto
09 : Pietra Campos
10 : João Felipe Carvalho

A primeira linha é omitida por ser justamente o cabeçalho.

Escrevendo um arquivo CSV

Para escrever um arquivo CSV usa-se a a função writer():

from csv import writer
from random import uniform

grade = lambda: round(uniform(0.0, 10.0), 2)

data = ((i + 1, grade(), grade()) for i in range(10))

with open("grades.csv", "w") as f:
    w = writer(f)
    w.writerow(["id", "nota1", "nota2"])
    w.writerows(data)

Este  exemplo usa dois métodos, .writerow() e .writerows(), o primeiro cuida de escrever uma única linha do arquivo (no caso a uso com o cabeçalho) enquanto que o outro utiliza um iterável e permite escrever múltiplas linhas (o conteúdo de ‘data’)…

$ python write_csv_file.py
$ cat grades.csv
id,nota1,nota2
1,7.48,4.89
2,1.79,0.63
3,3.13,7.17
4,0.32,4.88
5,4.54,4.68
6,7.17,1.31
7,3.28,8.85
8,0.96,2.96
9,4.84,8.58
10,7.94,9.3

Este exemplo pode ser expandido para produzir mais valores, mas creio que dois sejam suficientes por enquanto.

Lendo e gravando arquivos CSV

Já que ambos os arquivos, “students.csv” e “grades.csv”, encontram-se na mesma ordem fica fácil de mesclá-los em um só arquivo:

from csv import reader, writer

with open("students.csv", "r") as sf, open(
    "grades.csv", "r"
) as gf, open("joined.csv", "w") as jf:

    joined = writer(jf)

    for student, grade in zip(reader(sf), reader(gf)):
        joined.writerow(student + grade[1:])

Daí rodar…

$ python join_students_and_grades.py

E verificar o resultado…

csv-python-1_arquivos_unificados

Aliás, o programa usado para a visualização dos arquivos CSV chama-se VisiData e ele também exibe JSON, SQLite, XLS, XLSX etc… 🙂

Usando o CSV como um dicionário

Mas trabalhar com as informações em listas é pouco prático, certo? É possível usar a classe DictReader para importar o arquivo CSV para um dicionário.

from csv import DictReader
from json import dumps

with open("students.csv", "r") as f:
    students = [record for record in DictReader(f)]

print(dumps(students[8], indent=4))

A primeira linha do arquivo, o cabeçalho, será utilizada por padrão para indicar o nome das chaves caso o argumento “fieldnames=” seja omitido na criação do objeto.

$ python read_csv_file_as_dict.py 
{
    "id": "9",
    "nome": "Pietra Campos",
    "endere\u00e7o": "Favela Nat\u00e1lia Caldeira, 88",
    "bairro": "Xodo-Marize",
    "cidade": "Farias",
    "uf": "Paran\u00e1",
    "cep": "33447-453"
}

Para escrever um dicionário no formato de um arquivo CSV usa-se a classe DictWriter:

from csv import DictReader, DictWriter
from random import uniform

KEYS_TO_KEEP = ("id", "nome",)

CLASSES = (
    "biologia", "educação física", "filosofia", "física",
    "geografia", "história", "literatura", "língua estrangeira",
    "língua portuguesa", "matemática", "química",
)

grade = lambda: round(uniform(0.0, 10.0), 2)

with open("classes.csv", "w") as f:
    w = DictWriter(f, fieldnames=list(KEYS_TO_KEEP + CLASSES))
    w.writeheader()
    for student in DictReader(open("students.csv", "r")):
        w.writerow({
            **{key: student[key] for key in KEYS_TO_KEEP},
            **{class_name: grade() for class_name in CLASSES},
        })

O método .writeheader() cuida de escrever o cabeçalho do CSV enquanto que .writerow() escreve a combinação de parte dos valores do arquivo de entrada com outros cujos valores são sorteados ao acaso. O .writerows() até poderia ser usado mas deixaria o código confuso.

E diferente do DictReader, no DictWriter o argumento “fieldnames=” é obrigatório!

E quando o CSV não é um arquivo?

Até aqui todos os exemplos  utilizaram um file object para referência ao arquivo a ser lido ou criado. Mas o que fazer quando o CSV de entrada, saída ou ambos não é um arquivo físico?

A melhor solução é usar o StringIO para criar um dispositivo de E/S em memória:

from csv import reader, writer
from io import StringIO

fake_csv_file = (
    "SP,São Paulo\n" + "RJ,Rio de Janeiro\n"
    + "ES,Espírito Santo\n" + "MG,Minas Gerais\n"
)

input_file = StringIO(fake_csv_file)
output_file = StringIO()

data = sorted(reader(input_file))

w = writer(output_file)
w.writerows(data)

print(output_file.getvalue())

Que ao ser executado, produz…

$ python csv_as_variable.py 
ES,Espírito Santo
MG,Minas Gerais
RJ,Rio de Janeiro
SP,São Paulo

Este exemplo inclui um “arquivo” CSV na forma de string, que é lido pela função reader() e convertida em uma lista. quando é devidamente ordenada antes de se usar a função writer() para escrevê-la novamente como um “arquivo” CSV.

A ordenação é proposital e serve para mostrar que o conteúdo foi modificado.

Finalizando

Relembrando que o formato de um arquivo CSV não é algo oficialmente definido e com isto pode haver pequenas diferenças na forma como eles são construídos e uma boa forma de demonstrar isto é recorrendo à caixa de diálogo de exportação para CSV do próprio LibreOffice:

csv-python-1_libreoffice

Para isto o módulo csv conta o recurso dos dialetos que agrupam parâmetros específicos a serem usados pelo reader() e writer() como também há a opção de se configurar os parãmetros de formatação individualmente (caractere separador, delimitação de texto etc) e assim ajustá-lo para o arquivo que se está lendo ou que precisa ser escrito.

E no repositório do blog no GitHub estão disponíveis os exemplos usados aqui (com um pouco mais de organização e comentários),

Até!

Um comentário sobre “Usando arquivos CSV em Python

  1. Pingback: Utilizando o FastAPI – parte 1 | giovannireisnunes

Os comentários estão desativados.