Como escrever uma linha de cabeçalho com csv.DictWriter?

114

Suponha que eu tenha um csv.DictReaderobjeto e desejo escrevê-lo como um arquivo CSV. Como posso fazer isso?

Eu sei que posso escrever as linhas de dados assim:

dr = csv.DictReader(open(f), delimiter='\t')
# process my dr object
# ...
# write out object
output = csv.DictWriter(open(f2, 'w'), delimiter='\t')
for item in dr:
    output.writerow(item)

Mas como posso incluir os nomes dos campos?

martineau
fonte

Respostas:

149

Edit:
Em 2.7 / 3.2, há um novo writeheader()método . Além disso, a resposta de John Machin fornece um método mais simples de escrever a linha do cabeçalho.
Exemplo simples de uso do writeheader()método agora disponível em 2.7 / 3.2:

from collections import OrderedDict
ordered_fieldnames = OrderedDict([('field1',None),('field2',None)])
with open(outfile,'wb') as fou:
    dw = csv.DictWriter(fou, delimiter='\t', fieldnames=ordered_fieldnames)
    dw.writeheader()
    # continue on to write data

Instanciar DictWriter requer um argumento fieldnames.
Da documentação :

O parâmetro fieldnames identifica a ordem em que os valores no dicionário passados ​​para o método writerow () são gravados no csvfile.

Colocado de outra forma: o argumento Fieldnames é necessário porque os dictos do Python são inerentemente desordenados.
Abaixo está um exemplo de como você gravaria o cabeçalho e os dados em um arquivo.
Nota: withdeclaração foi adicionada em 2.6. Se estiver usando 2,5:from __future__ import with_statement

with open(infile,'rb') as fin:
    dr = csv.DictReader(fin, delimiter='\t')

# dr.fieldnames contains values from first row of `f`.
with open(outfile,'wb') as fou:
    dw = csv.DictWriter(fou, delimiter='\t', fieldnames=dr.fieldnames)
    headers = {} 
    for n in dw.fieldnames:
        headers[n] = n
    dw.writerow(headers)
    for row in dr:
        dw.writerow(row)

Como @FM menciona em um comentário, você pode condensar a escrita do cabeçalho em uma linha, por exemplo:

with open(outfile,'wb') as fou:
    dw = csv.DictWriter(fou, delimiter='\t', fieldnames=dr.fieldnames)
    dw.writerow(dict((fn,fn) for fn in dr.fieldnames))
    for row in dr:
        dw.writerow(row)
carne_mecânica
fonte
12
+1 Contudo uma outra maneira de escrever o cabeçalho: dw.writerow( dict((f,f) for f in dr.fieldnames) ).
FMc
2
@Adam: para uma linha mais curta, veja minha resposta.
John Machin
2
@John: +1 em sua resposta; simplesmente utilizar "a instância subjacente do escritor" é certamente preferível ao "laborioso mapeamento de identidade".
Mechanical_meat
1
@endolith: obrigado pelo feedback. Moveu essa parte para o topo da resposta.
Mechanical_meat
1
Como você também está usando um dictReader, é fácil adicionar os campos com dw = csv.DictWriter(fou, delimiter='\t', fieldnames=dr.fieldnames). Dessa forma, se seus campos mudarem, você não precisará ajustar o dictWriter.
Spencer Rathbun
29

Algumas opções:

(1) Laboriosamente, faça um ditado de mapeamento de identidade (ou seja, não faça nada) de seus nomes de campo para que csv.DictWriter possa convertê-lo de volta em uma lista e passá-lo para uma instância csv.writer.

(2) A documentação menciona "a writerinstância subjacente " ... então apenas use-a (exemplo no final).

dw.writer.writerow(dw.fieldnames)

(3) Evite a sobrecarga csv.Dictwriter e faça você mesmo com csv.writer

Gravando dados:

w.writerow([d[k] for k in fieldnames])

ou

w.writerow([d.get(k, restval) for k in fieldnames])

Em vez da extrasaction"funcionalidade", prefiro codificá-lo sozinho; dessa forma, você pode relatar TODOS os "extras" com as chaves e valores, não apenas a primeira chave extra. O que é realmente um incômodo com DictWriter é que se você verificou as chaves à medida que cada dicionário estava sendo construído, você precisa se lembrar de usar extrasaction = 'ignore', caso contrário, LENTAMENTE (nomes de campo é uma lista) repetir a verificação:

wrong_fields = [k for k in rowdict if k not in self.fieldnames]

============

>>> f = open('csvtest.csv', 'wb')
>>> import csv
>>> fns = 'foo bar zot'.split()
>>> dw = csv.DictWriter(f, fns, restval='Huh?')
# dw.writefieldnames(fns) -- no such animal
>>> dw.writerow(fns) # no such luck, it can't imagine what to do with a list
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\python26\lib\csv.py", line 144, in writerow
    return self.writer.writerow(self._dict_to_list(rowdict))
  File "C:\python26\lib\csv.py", line 141, in _dict_to_list
    return [rowdict.get(key, self.restval) for key in self.fieldnames]
AttributeError: 'list' object has no attribute 'get'
>>> dir(dw)
['__doc__', '__init__', '__module__', '_dict_to_list', 'extrasaction', 'fieldnam
es', 'restval', 'writer', 'writerow', 'writerows']
# eureka
>>> dw.writer.writerow(dw.fieldnames)
>>> dw.writerow({'foo':'oof'})
>>> f.close()
>>> open('csvtest.csv', 'rb').read()
'foo,bar,zot\r\noof,Huh?,Huh?\r\n'
>>>
John Machin
fonte
Atualmente no Python 3.6, a extrasactionfuncionalidade parece ser implementada melhor. Agora está wrong_fields = rowdict.keys() - self.fieldnames so it's effectively a definida a operação.
martineau
Estou votando esta resposta para o comentário "evitar DictWriter" - não vi nenhuma vantagem em usá-lo e parece mais rápido estruturar seus dados e usar csv.writer
neófito
8

Outra maneira de fazer isso seria adicionar antes de adicionar linhas em sua saída, a seguinte linha:

output.writerow(dict(zip(dr.fieldnames, dr.fieldnames)))

O zip retornaria uma lista de dupleto contendo o mesmo valor. Esta lista pode ser usada para iniciar um dicionário.

Raphael Pr
fonte