base64, кодирующий строки unicode в python 2.7

У меня есть строка Unicode, полученная из веб-службы с помощью модуля requests, который содержит байты двоичного документа (PCL, как это бывает). Один из этих байтов имеет значение 248, и попытка закодировать его в base64 приводит к следующей ошибке:

In [68]: base64.b64encode(response_dict['content']+'\n')
---------------------------------------------------------------------------
UnicodeEncodeError                        Traceback (most recent call last)
C:\...\<ipython-input-68-8c1f1913eb52> in <module>()
----> 1 base64.b64encode(response_dict['content']+'\n')

C:\Python27\Lib\base64.pyc in b64encode(s, altchars)
     51     """
     52     # Strip off the trailing newline
---> 53     encoded = binascii.b2a_base64(s)[:-1]
     54     if altchars is not None:
     55         return _translate(encoded, {'+': altchars[0], '/': altchars[1]})

UnicodeEncodeError: 'ascii' codec can't encode character u'\xf8' in position 272: ordinal not in range(128)

In [69]: response_dict['content'].encode('base64')
---------------------------------------------------------------------------
UnicodeEncodeError                        Traceback (most recent call last)
C:\...\<ipython-input-69-7fd349f35f04> in <module>()
----> 1 response_dict['content'].encode('base64')

C:\...\base64_codec.pyc in base64_encode(input, errors)
     22     """
     23     assert errors == 'strict'
---> 24     output = base64.encodestring(input)
     25     return (output, len(input))
     26

C:\Python27\Lib\base64.pyc in encodestring(s)
    313     for i in range(0, len(s), MAXBINSIZE):
    314         chunk = s[i : i + MAXBINSIZE]
--> 315         pieces.append(binascii.b2a_base64(chunk))
    316     return "".join(pieces)
    317

UnicodeEncodeError: 'ascii' codec can't encode character u'\xf8' in position 44: ordinal not in range(128)

Я нахожу это немного удивительным, потому что 248 находится в пределах диапазона беззнакового байта (и может содержаться в байтовой строке), но мой реальный вопрос: какой лучший или правильный способ закодировать эту строку?

Моя текущая работа такова:

In [74]: byte_string = ''.join(map(compose(chr, ord), response_dict['content']))

In [75]: byte_string[272]
Out[75]: '\xf8'

Похоже, это работает правильно, и результирующий byte_string может быть закодирован в base64, но похоже, что должен быть лучший способ. Есть?


person Marcin    schedule 05.03.2012    source источник
comment
248 может находиться в диапазоне беззнакового байта, но не в диапазоне стандартизированного ASCII [0-127].   -  person Cameron    schedule 05.03.2012
comment
@Cameron: Верный и хороший момент, но он все еще не объясняет проблему, поскольку точно такое же значение, хранящееся в строке байтов, не приводит к этой ошибке.   -  person Marcin    schedule 05.03.2012
comment
Смотрите мой ответ :-) Что вы сделали, так это взяли кодовые точки строки unicode и обработали их как байты. Это... в лучшем случае подозрительно, так как нет гарантии, что кодовые точки находятся даже в диапазоне 0-255. Что еще хуже, так это то, что никто больше не будет знать, как интерпретировать строку байтов позже, поскольку она находится в пользовательской неопределенной кодировке.   -  person Cameron    schedule 05.03.2012
comment
@Cameron: повторюсь: эти данные не являются кодовыми точками символов, это двоичные данные.   -  person Marcin    schedule 05.03.2012


Ответы (5)


Поскольку вы работаете с двоичными данными, я не уверен, что использовать кодировку utf-8 — хорошая идея. Я думаю, это зависит от того, как вы собираетесь использовать представление в кодировке base64. Я думаю, было бы лучше, если бы вы могли получать данные в виде строки байтов, а не строки Unicode. Я никогда не использовал библиотеку запросов, но просмотр документации показывает, что это возможно. Есть разделы, в которых говорится о «Двоичном содержимом ответа» и «Необработанном содержимом ответа».

person Dan Gerhardsson    schedule 05.03.2012
comment
Спасибо! Оказывается, кодирование как latin-1 дает точно такую ​​же последовательность байтов, что и мой обходной путь. - person Marcin; 05.03.2012
comment
@Marcin: вам нужно убедиться, что модуль запросов не предположил, что вы работаете с текстом, применил кодировку по умолчанию и декодировал ваши двоичные данные в юникод. Если это так, у вас проблемы. Можете ли вы убедиться, что контент соответствует вашим ожиданиям? - person Dan Gerhardsson; 05.03.2012
comment
Уделив немного больше внимания документам, оказалось, что запросы также сообщают мне кодировку, которая используется для декодирования ответа в юникод, поэтому я всегда могу надежно перекодировать с этим (и это снова дает те же байты ). - person Marcin; 05.03.2012

У вас есть строка unicode, которую вы хотите закодировать в base64. Проблема в том, что b64encode() работает только с байтами, а не с символами. Итак, вам нужно преобразовать строку unicode (которая представляет собой последовательность абстрактных кодовых точек Unicode) в строку байтов.

Преобразование абстрактных строк Unicode в конкретные последовательности байтов называется кодированием. Python поддерживает несколько кодировок; Я предлагаю широко используемую кодировку UTF-8:

byte_string = response_dict['content'].encode('utf-8')

Обратите внимание, что тот, кто декодирует байты, также должен знать, какая кодировка использовалась для возврата строки unicode с помощью дополнительной функции decode():

# Decode
decoded = byte_string.decode('utf-8')

Хорошей отправной точкой для получения дополнительной информации о Unicode и кодировках является документы по Python и эта статья Джоэла Спольски.

person Cameron    schedule 05.03.2012
comment
Чтобы было ясно: содержимое моей строки юникода является двоичными данными. Я не могу изменить их на несколько разных байтов. Есть ли кодировка личности? - person Marcin; 05.03.2012
comment
@Marcin: у вас не может быть строки unicode, содержащей двоичные данные. Это противоречие в терминах! Если предполагается, что байты строки unicode представляют двоичные данные (как здесь, кажется), то они не должны храниться в объекте unicode, так как это вообще не Unicode! - person Cameron; 05.03.2012
comment
Почему бы не добавить спецификацию? На самом деле эта функция помогает определить, является ли строка UTF-8 или нет. - person sebix; 03.09.2015
comment
@sebix: я думаю, что лучше всего, если спецификации обычно используются только в начале файлов; накладные расходы и сложность проверки строк повсюду для спецификации кажутся слишком высокими. Однако я перепутал кодировку, -sig делает добавление спецификации. - person Cameron; 03.09.2015

Я бы предложил сначала закодировать его во что-то вроде UTF-8 перед кодировкой base64:

In [12]: my_unicode = u'\xf8'

In [13]: my_utf8 = my_unicode.encode('utf-8')

In [15]: base64.b64encode(my_utf8)
Out[15]: 'w7g='
person Simon Jagoe    schedule 05.03.2012
comment
кодирование в UTF-8 не имеет смысла. либо вы кодируете из UTF-8 в байты/ascii, либо декодируете из ascii в UTF-8. это наоборот. - person sebix; 03.09.2015

Должна быть возможность получить ответ в виде двоичных байтов и полностью пропустить этапы декодирования и кодирования. Всегда есть вероятность, что requests выберет кодировку, при которой некоторые данные или ошибки будут потеряны при передаче туда и обратно.

Эта часть документации называется "Binary Response Content". " кажется, идеально подходит для вашей проблемы.

person Mark Ransom    schedule 05.03.2012

Если это двоичные данные... зачем вообще кодировать/декодировать? Особенно часть «base64.encodestring». Ниже показано, как я кодирую изображения в base64 для добавления непосредственно в мой код Python вместо дополнительных файлов. 2.7.2 кстати

import base64
iconfile = open("blah.icon","rb")
icondata = iconfile.read()
icondata = base64.b64encode(icondata)
person SpootDev    schedule 05.03.2012