Как преобразовать строки с данными в юникоде в кодировку latin-1 csv в Py2 и Py3?

#python #python-3.x #python-2.7

#python #python-3.x #python-2.7

Вопрос:

Я хочу преобразовать вложенный список, содержащий значения в Юникоде, в csv-кодировку latin-1 (чтобы я мог передать результат в веб-ответе и открыть файл в локальном Excel конечного пользователя).

Мы переходим на Py3, поэтому предпочтительно, чтобы один и тот же код работал как для Py2, так и для Py3 (по причинам обслуживания и покрытия).

Наш код на Python 2, который работает (для py2):

 from cStringIO import StringIO

def rows_to_csv_data(rows):
    rows = [[col.encode('latin-1') for col in row] for row in rows]
    buf = StringIO()
    writer = csv.writer(buf)
    writer.writerows(rows)
    return buf.getvalue()
  

Простой тестовый пример:

 def test_rows_to_csv_data():
    rows = [
        [u'helloæ', u'worldø']
    ]
    binary_data = rows_to_csv_data(rows)
    assert binary_data == u"helloæ,worldørn".encode('latin-1')

    # Update: the data is never written to a file, but sent with a web response:
    response = http.HttpResponse(content_type='text/csv')
    response['Content-Disposition'] = 'attachment; filename=hello.csv'
    response.write(binary_data)
    assert response.serialize() == b'Content-Type: text/csvrnContent-Disposition: attachment; filename=hello.csvrnrnhelloxe6,worldxf8rn'
  

Я не смог найти никакого удобного способа сделать это, используя библиотеки future или six.

Использование from io import StringIO дает мне (Py3):

 Expected :b'helloxe6,worldxf8rn'
Actual   :b'hello\xe6',b'world\xf8'rn
  

и Py2:

 >       writer.writerows(rows)
E       TypeError: unicode argument expected, got 'str'
  

Использование from io import BytesIO as StringIO работает для Py2, но Py3 дает:

 rows = [[b'helloxe6', b'worldxf8']]

    def rows_to_csv_data(rows):
        rows = [[col.encode('latin-1') for col in row] for row in rows]
        buf = StringIO()
        writer = csv.writer(buf)
>       writer.writerows(rows)
E       TypeError: a bytes-like object is required, not 'str'
  

какое сообщение об ошибке я не понимаю в этом контексте…

Возможно ли написать единую функцию, которая работает для обоих Python, или мне нужна полностью отдельная функция для Py3?

Комментарии:

1. Подождите минуту. Вам нужна функция, которая принимает список списков unicode в качестве входных данных и возвращает текст CSV? Для меня это звучит как плохая идея. Запись CSV в py3 и py2 отличается — в py3 вам нужно открыть файл с помощью newline='' , а в py2 вам нужно открыть его в двоичном режиме. Если у вас есть функция, которая возвращает строку CSV, вы все равно напутаете позже, когда фактически запишете эту строку в файл.

2. @Aran-Fey строка никогда не будет записана в файл, но включена в объект Django HttpResponse . .. и если быть очень педантичным .. она возвращает двоичные данные в кодировке latin-1, представляющие значения, разделенные запятыми (это точный формат, который принимает локальный Excel).

3. @Aran-Fey Я обновил тест с учетом предполагаемого варианта использования.

4. csv модуль в Python 3 использует строки в Юникоде для ввода. csv модуль в Python 2 использует байтовые строки для ввода. Они не очень совместимы, но сторонний unicodecsv модуль для Python 2 ближе к использованию в Python 3.

Ответ №1:

Вот иллюстрация различий между Python 2 и 3, которая проходит ваш тест. Протестировано на Python 2.7 и Python 3.6.

 #!coding:utf8
import io
import csv
import sys

def rows_to_csv_data(rows):
    if sys.version_info.major == 2:
        rows = [[col.encode('latin1') for col in row] for row in rows]
        buf = io.BytesIO()
    else:
        buf = io.StringIO(newline='')

    writer = csv.writer(buf)
    writer.writerows(rows)

    if sys.version_info.major == 2:
        return buf.getvalue()
    else:
        return buf.getvalue().encode('latin1')

def test_rows_to_csv_data():
    rows = [[u'helloæ', u'worldø']]
    binary_data = rows_to_csv_data(rows)
    assert binary_data == u"helloæ,worldørn".encode('latin-1')

test_rows_to_csv_data()
  

Комментарии:

1. Обратите внимание, что это приведет к сбою в python2, если rows содержит какие-либо нестроковые данные (т. Е. Числа). Возможно, вы захотите сделать это преобразование str-> bytes немного более надежным.

2. @Aran Обратите внимание, что это был исходный код OP. Это не было частью вопроса.

3. @Aran-Fey мой реальный код преобразования str-> bytes обрабатывает «все случаи», но он казался ортогональным вопросу, поэтому я его опустил. Хороший момент, хотя 🙂