Comment écrire une ligne d'en-tête avec csv.DictWriter?

114

Supposons que j'ai un csv.DictReaderobjet et que je souhaite l'écrire sous forme de fichier CSV. Comment puis-je faire ceci?

Je sais que je peux écrire les lignes de données comme ceci:

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)

Mais comment puis-je inclure les noms de champ?

Martineau
la source

Réponses:

149

Edit:
Dans 2.7 / 3.2 il y a une nouvelle writeheader()méthode . De plus, la réponse de John Machin fournit une méthode plus simple pour écrire la ligne d'en-tête.
Exemple simple d'utilisation de la writeheader()méthode désormais disponible en 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

L'instanciation de DictWriter nécessite un argument fieldnames.
De la documentation :

Le paramètre fieldnames identifie l'ordre dans lequel les valeurs du dictionnaire passées à la méthode writerow () sont écrites dans le fichier csv.

En d'autres termes: l'argument Fieldnames est obligatoire car les dictionnaires Python ne sont pas ordonnés par nature.
Voici un exemple de la façon dont vous écrivez l'en-tête et les données dans un fichier.
Remarque: une withdéclaration a été ajoutée en 2.6. Si vous utilisez 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)

Comme @FM le mentionne dans un commentaire, vous pouvez condenser l'écriture d'en-tête en une seule ligne, par exemple:

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)
viande_mécanique
la source
12
+1 Encore une autre façon d'écrire l' en- tête: dw.writerow( dict((f,f) for f in dr.fieldnames) ).
FMc
2
@Adam: pour un one-liner plus court, voyez ma réponse.
John Machin
2
@John: +1 à votre réponse; utiliser simplement "l'instance d'écrivain sous-jacente" est certainement préférable à une "cartographie d'identité laborieuse".
mechanical_meat
1
@endolith: merci pour les commentaires. Déplacement de cette partie en haut de la réponse.
mechanical_meat
1
Puisque vous utilisez également un dictReader, il est facile d'ajouter les champs avec dw = csv.DictWriter(fou, delimiter='\t', fieldnames=dr.fieldnames). De cette façon, si vos champs changent, vous n'avez pas besoin d'ajuster le dictWriter.
Spencer Rathbun
29

Quelques options:

(1) Faites laborieusement un mappage d'identité (c'est-à-dire ne rien faire) de vos noms de champ afin que csv.DictWriter puisse le reconvertir en liste et le transmettre à une instance de csv.writer.

(2) La documentation mentionne "l' writerinstance sous-jacente " ... alors utilisez-la simplement (exemple à la fin).

dw.writer.writerow(dw.fieldnames)

(3) Évitez la surcharge de csv.Dictwriter et faites-le vous-même avec csv.writer

Écriture des données:

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

ou

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

Au lieu de la extrasaction«fonctionnalité», je préfère la coder moi-même; de cette façon, vous pouvez signaler TOUS les "extras" avec les clés et les valeurs, pas seulement la première clé supplémentaire. Ce qui est vraiment gênant avec DictWriter, c'est que si vous avez vérifié vous-même les clés lors de la construction de chaque dict, vous devez vous rappeler d'utiliser extrasaction = 'ignore', sinon il va répéter LENTEMENT (les noms de champ est une liste) la vérification:

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
la source
Actuellement dans Python 3.6, la extrasactionfonctionnalité semble mieux implémentée. C'est maintenant l' wrong_fields = rowdict.keys() - self.fieldnames so it's effectively a opération définie.
martineau
Je vote cette réponse pour le commentaire `` éviter DictWriter '' - je n'ai vu aucun avantage à l'utiliser, et semble plus rapide à structurer vos données et à utiliser csv.writer
neophytte
8

Une autre façon de faire cela serait d'ajouter avant d'ajouter des lignes dans votre sortie, la ligne suivante:

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

Le zip renverrait une liste de doublets contenant la même valeur. Cette liste peut être utilisée pour lancer un dictionnaire.

Raphael Pr
la source