web-dev-qa-db-fra.com

Prise en charge générale Unicode / UTF-8 pour les fichiers csv dans Python 2.6

Le module csv dans Python ne fonctionne pas correctement quand il y a UTF-8/Unicode impliqué. J'ai trouvé, dans la documentation Python et sur d'autres pages Web, des extraits qui travaillez pour des cas spécifiques, mais vous devez bien comprendre l'encodage que vous gérez et utiliser l'extrait de code approprié.

Comment puis-je lire et écrire des chaînes et des chaînes Unicode à partir de fichiers .csv qui "fonctionnent juste" dans Python 2.6? Ou s'agit-il d'une limitation de Python 2.6 qui n'a pas de solution simple?

43
djen

L'exemple de code de lecture d'Unicode donné à http://docs.python.org/library/csv.html#examples semble obsolète, car il ne fonctionne pas avec Python 2.6 et 2.7.

Voici UnicodeDictReader qui fonctionne avec utf-8 et peut être avec d'autres encodages, mais je ne l'ai testé que sur les entrées utf-8.

L'idée en bref est de décoder Unicode uniquement après qu'une ligne csv a été divisée en champs par csv.reader.

class UnicodeCsvReader(object):
    def __init__(self, f, encoding="utf-8", **kwargs):
        self.csv_reader = csv.reader(f, **kwargs)
        self.encoding = encoding

    def __iter__(self):
        return self

    def next(self):
        # read and split the csv row into fields
        row = self.csv_reader.next() 
        # now decode
        return [unicode(cell, self.encoding) for cell in row]

    @property
    def line_num(self):
        return self.csv_reader.line_num

class UnicodeDictReader(csv.DictReader):
    def __init__(self, f, encoding="utf-8", fieldnames=None, **kwds):
        csv.DictReader.__init__(self, f, fieldnames=fieldnames, **kwds)
        self.reader = UnicodeCsvReader(f, encoding=encoding, **kwds)

Utilisation (l'encodage du fichier source est utf-8):

csv_lines = (
    "абв,123",
    "где,456",
)

for row in UnicodeCsvReader(csv_lines):
    for col in row:
        print(type(col), col)

Production:

$ python test.py
<type 'unicode'> абв
<type 'unicode'> 123
<type 'unicode'> где
<type 'unicode'> 456
52
Maxim Egorushkin

Une réponse un peu tardive, mais j'ai utilisé nicodecsv avec beaucoup de succès.

32
Serafeim

Le module fourni ici , ressemble à un remplacement sympa et simple du module csv qui vous permet de travailler avec utf-8 csv.

import ucsv as csv
with open('some.csv', 'rb') as f:
    reader = csv.reader(f)
    for row in reader:
        print row
22
itsadok

Il y a déjà l'utilisation de l'exemple Unicode dans ce doc , pourquoi encore besoin d'en trouver un autre ou de réinventer la roue?

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.reader(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]

def utf_8_encoder(unicode_csv_data):
    for line in unicode_csv_data:
        yield line.encode('utf-8')
7
YOU

Je confirme, unicodecsv est un excellent remplacement pour le module csv, je viens de remplacer csv par unicodecsv dans mon le code source, et cela fonctionne comme un charme.

4
GMLudo

L'encapsuleur unicode_csv_reader Mentionné dans la documentation python accepte les chaînes Unicode. En effet, csv n'accepte pas les chaînes Unicode. cvs n'est probablement pas au courant de l'encodage ou des paramètres régionaux et traite uniquement les chaînes qu'il obtient en octets. Donc, ce qui se passe, c'est que l'encapsuleur code les chaînes Unicode, ce qui signifie qu'il crée une chaîne d'octets. Ensuite, lorsque l'encapsuleur renvoie les résultats de csv, il décode à nouveau les octets, ce qui signifie qu'il convertit les séquences d'octets UTF-8 en caractères unicode corrects.

Si vous donnez à l'encapsuleur une chaîne d'octets simples, par exemple en utilisant f.readlines() cela donnera un UnicodeDecodeError sur les octets avec une valeur> 127. Vous utiliseriez le wrapper au cas où vous auriez dans votre programme des chaînes Unicode au format CSV.

Je peux imaginer que l'encapsuleur a toujours une limitation: puisque cvs n'accepte pas unicode, et il n'accepte pas non plus les délimiteurs multi-octets, vous ne pouvez pas analyser les fichiers qui ont un caractère unicode comme délimiteur.

3
gaston

Vous devriez considérer tablib , qui a une approche complètement différente, mais devrait être considéré sous la condition "ça marche juste".

with open('some.csv', 'rb') as f:
    csv = f.read().decode("utf-8")

import tablib
ds = tablib.Dataset()
ds.csv = csv
for row in ds.dict:
    print row["First name"]

Attention: tablib rejettera votre csv s'il n'a pas le même nombre d'éléments sur chaque ligne.

2
itsadok

C'est peut-être flagrant, mais pour les débutants, je le mentionnerai.

Dans le module python 3.X csvprend en charge tout encodage hors de la boîte , donc si vous utilisez cette version, vous pouvez vous en tenir au module standard.

 with open("foo.csv", encoding="utf-8") as f: 
     r = csv.reader(f, delimiter=";")
     for row in r: 
     print(row)

Pour une discussion supplémentaire, veuillez consulter: Est-ce que python 3.1.3 prend en charge unicode dans le module csv?

2
jb.

Voici une version légèrement améliorée de réponse de Maxim , qui peut également ignorer la nomenclature UTF-8:

import csv
import codecs

class UnicodeCsvReader(object):
    def __init__(self, csv_file, encoding='utf-8', **kwargs):
        if encoding == 'utf-8-sig':
            # convert from utf-8-sig (= UTF8 with BOM) to plain utf-8 (without BOM):
            self.csv_file = codecs.EncodedFile(csv_file, 'utf-8', 'utf-8-sig')
            encoding = 'utf-8'
        else:
            self.csv_file = csv_file
        self.csv_reader = csv.reader(self.csv_file, **kwargs)
        self.encoding = encoding

    def __iter__(self):
        return self

    def next(self):
        # read and split the csv row into fields
        row = self.csv_reader.next() 
        # now decode
        return [unicode(cell, self.encoding) for cell in row]

    @property
    def line_num(self):
        return self.csv_reader.line_num

class UnicodeDictReader(csv.DictReader):
    def __init__(self, csv_file, encoding='utf-8', fieldnames=None, **kwds):
        reader = UnicodeCsvReader(csv_file, encoding=encoding, **kwds)
        csv.DictReader.__init__(self, reader.csv_file, fieldnames=fieldnames, **kwds)
        self.reader = reader

Notez que la présence de la nomenclature n'est pas pas détectée automatiquement. Vous devez signaler qu'il est là en passant le encoding='utf-8-sig' argument au constructeur de UnicodeCsvReader ou UnicodeDictReader. Encodage utf-8-sig est utf-8 avec une nomenclature.

1
Matthias

J'ajouterais à la réponse de itsadok. Par défaut, Excel enregistre les fichiers csv en latin-1 (ce que ucsv ne prend pas en charge). Vous pouvez facilement résoudre ce problème en:

with codecs.open(csv_path, 'rb', 'latin-1') as f:
    f = StringIO.StringIO( f.read().encode('utf-8') )

reader = ucsv.UnicodeReader(f)
# etc.
0
user2426679