Autant que je sache, le module csv Python (v2.6) ne peut pas gérer les données Unicode par défaut, correct? Dans la documentation Python, il existe un exemple sur la façon de lire un fichier encodé en UTF-8. Mais cet exemple ne renvoie que les lignes CSV sous forme de liste. J'aimerais accéder aux colonnes de lignes par leur nom, comme cela est fait par csv.DictReader
mais avec un fichier d'entrée au format CSV codé UTF-8.
Quelqu'un peut-il me dire comment faire cela de manière efficace? Je vais devoir traiter des fichiers CSV avec une taille de 100 Mo.
Je suis venu avec une réponse moi-même:
def UnicodeDictReader(utf8_data, **kwargs):
csv_reader = csv.DictReader(utf8_data, **kwargs)
for row in csv_reader:
yield {unicode(key, 'utf-8'):unicode(value, 'utf-8') for key, value in row.iteritems()}
Note: Ceci a été mis à jour pour que les clés soient décodées selon la suggestion dans les commentaires
Une approche classifiée de la réponse @LMatter. Cette approche vous permet de bénéficier de tous les avantages de DictReader, tels que l’obtention des noms de champs et l’obtention du numéro de ligne, ainsi que la prise en charge du format UTF-8.
import csv
class UnicodeDictReader(csv.DictReader, object):
def next(self):
row = super(UnicodeDictReader, self).next()
return {unicode(key, 'utf-8'): unicode(value, 'utf-8') for key, value in row.iteritems()}
Tout d’abord, utilisez la version 2.6 de la documentation . Cela peut changer pour chaque version. Il indique clairement qu'il ne prend pas en charge Unicode, mais qu'il prend en charge UTF-8. Techniquement , ce ne sont pas la même chose. Comme le dit la documentation:
Le module csv ne prend pas directement en charge la lecture et l’écriture en Unicode, mais c’est une sauvegarde 8 bits en cas de problème avec certains problèmes liés aux caractères ASCII NUL. Vous pouvez donc écrire des fonctions ou des classes qui gèrent l'encodage et le décodage pour vous, à condition d'éviter les encodages tels que UTF-16 qui utilisent des NUL. UTF-8 est recommandé.
L'exemple ci-dessous (tiré de la documentation) montre comment créer deux fonctions qui lisent correctement le texte au format UTF-8 au format CSV. Vous devez savoir que csv.reader()
renvoie toujours un objet DictReader.
import csv
def unicode_csv_reader(unicode_csv_data, dialect=csv.Excel, **kwargs):
# csv.py doesn't do Unicode; encode temporarily as UTF-8:
csv_reader = csv.DictReader(utf_8_encoder(unicode_csv_data),
dialect=dialect, **kwargs)
for row in csv_reader:
# decode UTF-8 back to Unicode, cell by cell:
yield [unicode(cell, 'utf-8') for cell in row]
La réponse n'a pas les méthodes DictWriter
, alors voici la classe mise à jour:
class DictUnicodeWriter(object):
def __init__(self, f, fieldnames, dialect=csv.Excel, encoding="utf-8", **kwds):
self.fieldnames = fieldnames # list of keys for the dict
# 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, row):
self.writer.writerow({k: v.encode("utf-8") for k, v in row.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 row in rows:
self.writerow(row)
def writeheader(self):
header = dict(Zip(self.fieldnames, self.fieldnames))
self.writerow(header)
Le package csvw
a également d'autres fonctionnalités (pour les fichiers CSV enrichis de métadonnées pour le Web), mais il définit une classe UnicodeDictReader
entourant sa classe UnicodeReader
, ce qui en fait exactement ce qui suit:
class UnicodeReader(Iterator):
"""Read Unicode data from a csv file."""
[…]
def _next_row(self):
self.lineno += 1
return [
s if isinstance(s, text_type) else s.decode(self._reader_encoding)
for s in next(self.reader)]
Il m’a attrapé quelques fois, mais csvw.UnicodeDictReader
vraiment, vraiment doit être utilisé dans un bloc with
et se casse autrement. Autre que cela, le module est bien générique et compatible avec py2 et py3.