Python CSV DictReader con datos UTF-8

AFAIK, el módulo csv de Python (v2.6) no puede manejar datos Unicode de forma predeterminada, ¿correcto? En los documentos de Python hay un ejemplo sobre cómo leer un archivo codificado en UTF-8. Pero este ejemplo solo devuelve las filas CSV como una lista. Me gustaría acceder a las columnas de la fila por su nombre, ya que lo hace csv.DictReader pero con el archivo de entrada CSV codificado en UTF-8.

¿Alguien puede decirme cómo hacer esto de una manera eficiente? Tendré que procesar archivos CSV en cientos de MByte de tamaño.

Se me ocurrió una respuesta a mí mismo:

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

Nota: Esto se ha actualizado para que las claves se decodifiquen según la sugerencia de los comentarios.

En primer lugar, utilice la versión 2.6 de la documentación . Puede cambiar para cada lanzamiento. Dice claramente que no es compatible con Unicode pero sí con UTF-8. Técnicamente , estos no son lo mismo. Como dicen los doctores:

El módulo csv no admite directamente la lectura y la escritura de Unicode, pero es una limpieza de 8 bits, salvo por algunos problemas con los caracteres NUL ASCII. Por lo tanto, puede escribir funciones o clases que manejen la encoding y la deencoding siempre que evite las codificaciones como UTF-16 que utilizan NUL. Se recomienda UTF-8.

El siguiente ejemplo (de los documentos) muestra cómo crear dos funciones que leen correctamente el texto como UTF-8 como CSV. Debe saber que csv.reader() siempre devuelve un objeto 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] 

Un enfoque basado en la clase para la respuesta de @LMatter, con este enfoque usted todavía obtiene todos los beneficios de DictReader, como obtener los nombres de campo y obtener el número de línea más el que maneja 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()} 

El paquete csvw tiene otra funcionalidad (para CSV enriquecido con metadatos para la web), pero define una clase UnicodeDictReader a su clase UnicodeReader , que en su esencia hace exactamente eso:

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

Me sorprendió un par de veces, pero csvw.UnicodeDictReader realmente, realmente necesita ser usado en un bloque with y se rompe de otra manera Aparte de eso, el módulo es muy genérico y compatible con py2 y py3.

La respuesta no tiene los métodos de DictWriter , así que aquí está la clase actualizada:

 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) 

Para mí, la clave no estaba en la manipulación de los argumentos de DictReader csv, sino en el abridor de archivos en sí. Esto hizo el truco:

 with open(filepath, mode="r", encoding="utf-8-sig") as csv_file: csv_reader = csv.DictReader(csv_file) 

No se requiere clase especial. Ahora puedo abrir archivos con o sin BOM sin fallar.