Общая поддержка Unicode/UTF-8 для файлов csv в Python 2.6

Модуль csv в Python не работает должным образом, когда задействован UTF-8/Unicode. Я нашел в документацию Python и на других веб-страницах, фрагменты, которые работают для определенных случаев, но вы должны хорошо понимать, какую кодировку вы обрабатываете и используйте соответствующий фрагмент.

Как я могу читать и записывать строки и строки Unicode из .csv файлов, которые "просто работают" в Python 2.6? Или это ограничение Python 2.6, у которого нет простого решения?

Ответ 1

Пример кода для чтения Unicode, приведенного в http://docs.python.org/library/csv.html#examples, выглядит устаревшим, поскольку он не работает с Python 2.6 и 2.7.

Здесь следует UnicodeDictReader, который работает с utf-8 и может быть с другими кодировками, но я тестировал его только на входах utf-8.

Короче говоря, нужно декодировать Unicode только после того, как строка csv была разделена на поля 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)

Использование (исходное кодирование файла - utf-8):

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

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

Вывод:

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

Ответ 2

Немного поздний ответ, но я с большим успехом использовал unicodecsv.

Ответ 3

Модуль, предоставленный здесь, выглядит как классная, простая замена для модуля csv, которая позволяет работать с utf-8 csv.

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

Ответ 4

Существует пример использования Unicode в doc, почему еще нужно найти другой или повторно изобрести колесо?

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

Ответ 5

Я подтверждаю, unicodecsv - отличная замена для модуля csv, я только что заменил csv на unicodecsv в моем исходном коде, и он работает как шарм.

Ответ 6

Обертка unicode_csv_reader, упомянутая в документации python, принимает строки Unicode. Это связано с тем, что csv не принимает строки Unicode. cvs, вероятно, не знает о кодировке или локали и просто рассматривает строки, которые он получает как байты. Итак, что происходит, так это то, что оболочка кодирует строки Unicode, что означает, что она создает строку байтов. Затем, когда оболочка возвращает результаты csv, он снова декодирует байты, что означает, что он преобразует последовательности байтов UTF-8 в правильные символы юникода.

Если вы даете оболочке строку простого байта, например. используя f.readlines(), он даст UnicodeDecodeError в байтах со значением > 127. Вы использовали бы обертку, если у вас есть строки в юникоде в вашей программе, которые находятся в формате CSV.

Я могу представить, что оболочка все еще имеет одно ограничение: поскольку cvs не принимает unicode, а также не принимает многобайтовые разделители, вы не можете анализировать файлы, которые имеют символ unicode в качестве разделителя.

Ответ 7

Вы должны рассмотреть tablib, который имеет совершенно другой подход, но должен рассматриваться в соответствии с требованием "просто работает".

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

Предупреждение: tablib отклонит ваш csv, если он не имеет одинакового количества элементов в каждой строке.

Ответ 8

Может быть, это откровенно очевидно, но для начинающих я расскажу об этом.

В python 3.X csv module поддерживает любую кодировку из коробки, поэтому, если вы используете эту версию, вы можете придерживаться стандартный модуль.

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

Дополнительную информацию см. в разделе Поддерживает ли python 3.1.3 юникод в модуле csv?

Ответ 9

Вот немного улучшенная версия Ответ Maxim, который также может пропустить спецификацию 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

Обратите внимание, что наличие спецификации не определяется автоматически. Вы должны сообщить, что он есть, передав аргумент encoding='utf-8-sig' конструктору UnicodeCsvReader или UnicodeDictReader. Кодирование utf-8-sig имеет utf-8 с спецификацией.

Ответ 10

Я бы добавил к его ответу. По умолчанию excel сохраняет csv файлы как latin-1 (который не поддерживает ucsv). Вы можете легко исправить это:

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

reader = ucsv.UnicodeReader(f)
# etc.