Python DictWriter que escribe archivos CSV codificados en UTF-8

  1. Tengo una lista de diccionarios que contienen cadenas Unicode.
  2. csv.DictWriter puede escribir una lista de diccionarios en un archivo CSV.
  3. Quiero que el archivo CSV esté codificado en UTF8.
  4. El módulo csv no puede manejar la conversión de cadenas Unicode en UTF8.
  5. La documentación del módulo csv tiene un ejemplo para convertir todo a UTF8:

     def utf_8_encoder(unicode_csv_data): for line in unicode_csv_data: yield line.encode('utf-8') 
  6. También tiene una clase UnicodeWriter .

Pero … ¿cómo puedo hacer que DictWriter funcione con estos? ¿No tendrían que inyectarse a sí mismos en el medio para capturar los diccionarios desensamblados y codificarlos antes de que los escriba en el archivo? No lo entiendo

ACTUALIZACIÓN : El módulo unicodecsv de terceros implementa esta respuesta de 7 años para usted. Ejemplo debajo de este código. También hay una solución Python 3 que no requiere un módulo de terceros.

Original Python 2 Respuesta

Si usa Python 2.7 o posterior, use una comprensión de dictado para volver a asignar el diccionario a utf-8 antes de pasar a 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() 

Puede usar esta idea para actualizar UnicodeWriter a 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 unicodecsv Ejemplo:

 # 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:

Además, el módulo csv incorporado de Python 3 admite Unicode de forma nativa:

 # 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) 

Hay una solución simple utilizando el maravilloso módulo UnicodeCSV . Después de tenerlo, solo cambia la línea.

 import csv 

a

 import unicodecsv as csv 

Y automáticamente comienza a jugar bien con UTF-8.

Nota: Cambiar a Python 3 también lo librará de este problema (gracias a jamescampbell por la sugerencia). Y es algo que uno debería hacer de todos modos.

Puede convertir los valores a UTF-8 sobre la marcha al pasar el DictWriter.writerow() a DictWriter.writerow() . Por ejemplo:

 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() 

Salida foo.csv :

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

Puede usar alguna clase de proxy para codificar valores de dict según sea necesario, como esto:

 # -*- 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)) 

Cuando llama a csv.writer con su contenido, la idea es pasar el contenido a través de utf_8_encoder ya que le daría el contenido codificado (utf-8).

Mi solución es un poco diferente. Si bien todas las soluciones anteriores se enfocan en tener un dict compatible con Unicode, mi solución hace que DictWriter sea compatible con Unicode. Este enfoque incluso se sugiere en los documentos de Python ( 1 ).

Las clases UTF8Recoder, UnicodeReader, UnicodeWriter se toman de los documentos de Python. UnicodeWriter-> writerow también fue cambiado un poco.

Utilícelo como DictWriter / DictReader regular.

Aquí está el código:

 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)