#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 обрабатывает «все случаи», но он казался ортогональным вопросу, поэтому я его опустил. Хороший момент, хотя 🙂