web-dev-qa-db-fra.com

Python DictWriter écrit des fichiers CSV encodés en UTF-8

  1. J'ai une liste de dictionnaires contenant des chaînes unicode.
  2. csv.DictWriter peut écrire une liste de dictionnaires dans un fichier CSV.
  3. Je veux que le fichier CSV soit encodé en UTF8.
  4. Le module csv ne peut pas gérer la conversion de chaînes unicode en UTF8.
  5. La documentation du module csv contient un exemple pour tout convertir en UTF8:

    def utf_8_encoder(unicode_csv_data):
        for line in unicode_csv_data:
            yield line.encode('utf-8')
    
  6. Il a également une classe UnicodeWriter.

Mais ... comment faire fonctionner DictWriter avec ceux-ci? Ne devraient-ils pas s'injecter au milieu de celui-ci, attraper les dictionnaires désassemblés et les encoder avant qu'il ne les écrive dans le fichier? Je ne comprends pas.

50
endolith

[~ # ~] mise à jour [~ # ~] : le module tiers nicodecsv implémente cette réponse vieille de 7 ans pour vous. Exemple sous ce code. Il existe également une solution Python 3 qui ne nécessite pas de module tiers.

Original Python 2 Réponse

Si vous utilisez Python 2.7 ou version ultérieure, utilisez une compréhension dict pour remapper le dictionnaire vers utf-8 avant de passer à DictWriter:

# coding: utf-8
import csv
D = {'name':u'马克','pinyin':u'mǎkè'}
f = open('out.csv','wb')
f.write(u'\ufeff'.encode('utf8')) # BOM (optional...Excel needs it to open UTF-8 file properly)
w = csv.DictWriter(f,sorted(D.keys()))
w.writeheader()
w.writerow({k:v.encode('utf8') for k,v in D.items()})
f.close()

Vous pouvez utiliser cette idée pour mettre à jour UnicodeWriter vers DictUnicodeWriter: # coding: utf-8 import csv import cStringIO import codecs

class DictUnicodeWriter(object):

    def __init__(self, f, fieldnames, dialect=csv.Excel, encoding="utf-8", **kwds):
        # Redirect output to a queue
        self.queue = cStringIO.StringIO()
        self.writer = csv.DictWriter(self.queue, fieldnames, dialect=dialect, **kwds)
        self.stream = f
        self.encoder = codecs.getincrementalencoder(encoding)()

    def writerow(self, D):
        self.writer.writerow({k:v.encode("utf-8") for k,v in D.items()})
        # Fetch UTF-8 output from the queue ...
        data = self.queue.getvalue()
        data = data.decode("utf-8")
        # ... and reencode it into the target encoding
        data = self.encoder.encode(data)
        # write to the target stream
        self.stream.write(data)
        # empty queue
        self.queue.truncate(0)

    def writerows(self, rows):
        for D in rows:
            self.writerow(D)

    def writeheader(self):
        self.writer.writeheader()

D1 = {'name':u'马克','pinyin':u'Mǎkè'}
D2 = {'name':u'美国','pinyin':u'Měiguó'}
f = open('out.csv','wb')
f.write(u'\ufeff'.encode('utf8')) # BOM (optional...Excel needs it to open UTF-8 file properly)
w = DictUnicodeWriter(f,sorted(D.keys()))
w.writeheader()
w.writerows([D1,D2])
f.close()

Python 2 nicodecsv Exemple:

# coding: utf-8
import unicodecsv as csv

D = {u'name':u'马克',u'pinyin':u'mǎkè'}

with open('out.csv','wb') as f:
    w = csv.DictWriter(f,fieldnames=sorted(D.keys()),encoding='utf-8-sig')
    w.writeheader()
    w.writerow(D)

Python 3:

De plus, Python 3 prend en charge Unicode en natif:

# coding: utf-8
import csv

D = {u'name':u'马克',u'pinyin':u'mǎkè'}

# Use newline='' instead of 'wb' in Python 3.
with open('out.csv','w',encoding='utf-8-sig',newline='') as f:
    w = csv.DictWriter(f,fieldnames=sorted(D.keys()))
    w.writeheader()
    w.writerow(D)
88
Mark Tolonen

Il existe une solution de contournement simple en utilisant le merveilleux module nicodeCSV . Après l'avoir, il suffit de changer de ligne

import csv

à

import unicodecsv as csv

Et il commence automatiquement à jouer à Nice avec UTF-8.

Remarque: Passer à Python 3 vous débarrassera également de ce problème (merci jamescampbell et c'est quelque chose que l'on devrait faire de toute façon.

39
rlafuente

Vous pouvez convertir les valeurs en UTF-8 à la volée lorsque vous passez le dict à DictWriter.writerow(). Par exemple:

import csv

rows = [
    {'name': u'Anton\xedn Dvo\u0159\xe1k','country': u'\u010cesko'},
    {'name': u'Bj\xf6rk Gu\xf0mundsd\xf3ttir', 'country': u'\xcdsland'},
    {'name': u'S\xf8ren Kierkeg\xe5rd', 'country': u'Danmark'}
    ]

# implement this wrapper on 2.6 or lower if you need to output a header
class DictWriterEx(csv.DictWriter):
    def writeheader(self):
        header = dict(Zip(self.fieldnames, self.fieldnames))
        self.writerow(header)

out = open('foo.csv', 'wb')
writer = DictWriterEx(out, fieldnames=['name','country'])
# DictWriter.writeheader() was added in 2.7 (use class above for <= 2.6)
writer.writeheader()
for row in rows:
    writer.writerow(dict((k, v.encode('utf-8')) for k, v in row.iteritems()))
out.close()

Sortie foo.csv:

name,country
Antonín Dvořák,Česko
Björk Guðmundsdóttir,Ísland
Søren Kierkegård,Danmark
15
samplebias

Vous pouvez utiliser une classe proxy pour coder les valeurs dict selon les besoins, comme ceci:

# -*- coding: utf-8 -*- 
import csv
d = {'a':123,'b':456, 'c':u'Non-ASCII: проверка'}

class DictUnicodeProxy(object):
    def __init__(self, d):
        self.d = d
    def __iter__(self):
        return self.d.__iter__()
    def get(self, item, default=None):
        i = self.d.get(item, default)
        if isinstance(i, unicode):
            return i.encode('utf-8')
        return i

with open('some.csv', 'wb') as f:
    writer = csv.DictWriter(f, ['a', 'b', 'c'])
    writer.writerow(DictUnicodeProxy(d))
6
Daniel Kluev

Quand vous appelez csv.writer avec votre contenu, l'idée est de faire passer le contenu par utf_8_encoder car cela vous donnerait le contenu encodé (utf-8).

2
Senthil Kumaran

Ma solution est un peu différente. Bien que toutes les solutions ci-dessus se concentrent sur la dictée compatible Unicode, mes solutions rendent DictWriter compatible avec Unicode. Cette approche est même suggérée dans python docs ( 1 ).

Les classes UTF8Recoder, UnicodeReader, UnicodeWriter sont extraites de python docs. UnicodeWriter-> writerow a également été légèrement modifié.

Utilisez-le comme DictWriter/DictReader ordinaire.

Voici le code:

import csv, codecs, cStringIO

class UTF8Recoder:
    """
    Iterator that reads an encoded stream and reencodes the input to UTF-8
    """
    def __init__(self, f, encoding):
        self.reader = codecs.getreader(encoding)(f)

    def __iter__(self):
        return self

    def next(self):
        return self.reader.next().encode("utf-8")

class UnicodeReader:
    """
    A CSV reader which will iterate over lines in the CSV file "f",
    which is encoded in the given encoding.
    """

    def __init__(self, f, dialect=csv.Excel, encoding="utf-8", **kwds):
        f = UTF8Recoder(f, encoding)
        self.reader = csv.reader(f, dialect=dialect, **kwds)

    def next(self):
        row = self.reader.next()
        return [unicode(s, "utf-8") for s in row]

    def __iter__(self):
        return self

class UnicodeWriter:
    """
    A CSV writer which will write rows to CSV file "f",
    which is encoded in the given encoding.
    """

    def __init__(self, f, dialect=csv.Excel, encoding="utf-8", **kwds):
        # Redirect output to a queue
        self.queue = cStringIO.StringIO()
        self.writer = csv.writer(self.queue, dialect=dialect, **kwds)
        self.stream = f
        self.encoder = codecs.getincrementalencoder(encoding)()

    def writerow(self, row):
        self.writer.writerow([unicode(s).encode("utf-8") for s in row])
        # Fetch UTF-8 output from the queue ...
        data = self.queue.getvalue()
        data = data.decode("utf-8")
        # ... and reencode it into the target encoding
        data = self.encoder.encode(data)
        # write to the target stream
        self.stream.write(data)
        # empty queue
        self.queue.truncate(0)

    def writerows(self, rows):
        for row in rows:
            self.writerow(row)

class UnicodeDictWriter(csv.DictWriter, object):
    def __init__(self, f, fieldnames, restval="", extrasaction="raise", dialect="Excel", *args, **kwds):
        super(UnicodeDictWriter, self).__init__(f, fieldnames, restval="", extrasaction="raise", dialect="Excel", *args, **kwds)
        self.writer = UnicodeWriter(f, dialect, **kwds)
1
b1r3k