Вывод Python zlib, как восстановить таблицу mysql utf-8?

В python я сжал строку с помощью zlib, а затем вставил ее в столбец mysql типа blob, используя кодировку utf-8. Строка возвращается как utf-8, но неясно, как вернуть ее в формат, в котором я могу ее распаковать. Вот некоторый псевдо-вывод:

valueInserted = zlib.compress('a') = 'x\x9cK\x04\x00\x00b\x00b'

значениеFromSqlColumn = u'x\x9cK\x04\x00\x00b\x00b'

zlib.decompress (valueFromSqlColumn) UnicodeEncodeError: кодек ascii не может кодировать символ u'\x9c' в позиции 1: порядковый номер не в диапазоне (128)

если я это сделаю, он вставит несколько дополнительных символов:

valueFromSqlColumn.encode('utf-8') = 'x\xc2\x9cK\x04\x00\x00b\x00b'

Какие-либо предложения?


person Heinrich Schmetterling    schedule 24.10.2009    source источник
comment
Строка не возвращается как UTF-8, она возвращается как объект Unicode. Это важное и очень распространенное заблуждение.   -  person u0b34a0f6ae    schedule 25.10.2009


Ответы (3)


Unicode предназначен для совместимости с latin-1, поэтому попробуйте:

>>> import zlib
>>> u = zlib.compress("test").decode('latin1')
>>> u
u'x\x9c+I-.\x01\x00\x04]\x01\xc1'

А потом

>>> zlib.decompress(u.encode('latin1'))
'test'

EDIT: исправлена ​​опечатка, latin-1 не предназначен для совместимости с unicode, все наоборот.

person csl    schedule 24.10.2009
comment
Latin-1 и UTF-8 несовместимы - есть различия, когда вы выходите за пределы простого диапазона ASCII, что вы определенно будете делать, когда zlib сжимает что-то - person Blair Conrad; 25.10.2009
comment
@Blair: Имеется в виду, что все значения байта latin-1 соответствуют кодовой точке в Unicode с тем же значением. UTF-8 и latin-1 будут отображать их по-разному. - person u0b34a0f6ae; 25.10.2009
comment
csl: вы должны иметь в виду, что Unicode обратно совместим с Latin-1 (в одном аспекте), а не с UTF-8. - person u0b34a0f6ae; 25.10.2009
comment
означает ли это, что латиница-1 - неправильная кодировка? я буду zlib сжимать что-то латинское. - person Heinrich Schmetterling; 25.10.2009
comment
@Heinrich: сжатие возвращает байты (str), которые мы декодируем, чтобы получить строку юникода (юникод). Кодировка Latin-1 имеет то свойство, что кодовая точка latin-1 точно такая же, как кодовая точка Unicode (не наоборот, latin-1 имеет только 255 кодовых точек). Это гарантирует, что байты 'x\x9cK\x04\x00\x00b\x00b' будут переведены в те же кодовые точки в Юникоде u'x\x9cK\x04\x00\x00b\x00b'. - person u0b34a0f6ae; 25.10.2009
comment
@csl: Latin1 разработан для совместимости с UTF-8, что явно не соответствует действительности. @Heinrich: предоставленный код должен работать независимо от того, какой вы считаете кодировку, и должен работать, даже если несжатая строка не интерпретируется как текст, а представляет собой растровое изображение или какой-либо другой двоичный объект. .decode('latin1') - это просто волшебный трюк, чтобы завершить процесс, гарантирующий, что поездка в базу данных и обратно снова воспроизведет исходные сжатые данные. Обратите внимание, что я говорю должен работать... вы не разглашаете, что именно происходит в этой поездке. - person John Machin; 25.10.2009
comment
@kaiser.se: Ммм, назовите меня сумасшедшим, но я думал, что у latin1 256 кодовых точек; какой отсутствует? - person John Machin; 25.10.2009
comment
@Джон: Ты, наверное, прав! В тот момент, когда я писал предложение, я подумал, что 0, вероятно, не настоящий код. Но мы считаем, верно? - person u0b34a0f6ae; 25.10.2009
comment
@kaiser.se: Конечно, это настоящий код. Он представляет управляющий символ ASCII NUL. - person John Machin; 25.10.2009
comment
Да, ребята, я имел в виду наоборот. Спасибо. - person csl; 26.10.2009

У вас есть объект Unicode, который действительно кодирует байты. Это прискорбно, так как строки юникода должны быть только кодирующим текстом, верно?

В любом случае, мы хотим создать строку байтов. Это str в Python 2.x. Мы видим по напечатанной строке, которую вы дали u'x\x9cK\x04\x00\x00b\x00b', что значения байтов закодированы как кодовые точки Unicode. Мы можем получить числовое значение кода с помощью функции ord(..). Затем мы можем получить представление этого числа в виде строки байтов с помощью функции chr(..). Давайте попробуем это:

>>> ord(u"A")
65
>>> chr(_)
'A'

Таким образом, мы можем сами декодировать строку:

>>> udata = u'x\x9cK\x04\x00\x00b\x00b'
>>> bdata = "".join(chr(ord(uc)) for uc in udata)
>>> bdata
'x\x9cK\x04\x00\x00b\x00b'

(Подождите, что делает приведенный выше код? Что делает объединение? Сначала мы создаем список кодовых точек в строке:

>>> [ord(uc) for uc in udata]
[120, 156, 75, 4, 0, 0, 98, 0, 98]

Затем мы интерпретируем числа как байты, конвертируя их по отдельности:

>>> [chr(ord(uc)) for uc in udata]
['x', '\x9c', 'K', '\x04', '\x00', '\x00', 'b', '\x00', 'b']

Наконец, мы соединяем их с помощью "" в качестве разделителя, используя "".join(list-of-strings)

Конец ожидания..)

Тем не менее, cls умно отмечает, что кодировка Latin-1 имеет свойство, заключающееся в том, что значение байта символа в кодировке Latin-1 равно кодовой точке символа в Unicode. Учитывая, конечно, что символ находится в диапазоне от 0 до 255, где определена латиница-1. Это означает, что мы можем выполнить преобразование байтов непосредственно с помощью Latin-1:

>>> udata = u'x\x9cK\x04\x00\x00b\x00b'
>>> udata.encode("latin-1")
'x\x9cK\x04\x00\x00b\x00b'

Что, как видите, дает тот же результат.

person u0b34a0f6ae    schedule 24.10.2009

valueInserted = zlib.compress('a') = 'x\x9cK\x04\x00\x00b\x00b'

Обратите внимание, что это объект str. Вы говорите, что «вставили его в столбец mysql типа blob, используя кодировку utf-8». Поскольку сжатая строка является двоичной, а не текстовой, "BLOB" является подходящим типом столбца, но ЛЮБОЕ кодирование или другое преобразование - очень плохая идея. Вы должны иметь возможность восстанавливать из базы данных ТОЧНО вплоть до последнего бита того, что вы вставили, иначе распаковка завершится ошибкой, либо (менее вероятно, но хуже) молча производя мусор.

Вы говорите, что возвращаетесь после любого процесса, через который вы проходите, вставляя его и снова извлекая:

valueFromSqlColumn = u'x\x9cK\x04\x00\x00b\x00b'

Внимательно отметьте, что есть только одна крошечная визуальная разница: u'что-то' вместо 'что-то'. Это делает его объектом unicode. Основываясь на ваших собственных доказательствах, «возвращается как UTF-8» неверно. Объект unicode и объект str, закодированный в utf8, — это не одно и то же.

Угадайте 1: вставьте как необработанную строку, извлеките с декодированием latin1.

Угадайте 2: вставьте как сжатый.decode('latin1').encode('utf8'), извлеките с декодированием utf8.

Вам действительно нужно понимать процесс вставки и извлечения, в том числе то, что кодирует и декодирует по умолчанию.

Тогда вам действительно нужно исправить свой код. Однако тем временем вы, вероятно, можете собрать то, что у вас есть.

Обратите внимание:

>>> valueFromSqlColumn = u'x\x9cK\x04\x00\x00b\x00b'
>>> all(ord(char) <= 255 for char in valueFromSqlColumn)
True

Проведите несколько испытаний с более сложным вводом, чем «а». Если, как я предполагаю, вы видите, что все символы Юникода имеют порядковый номер в диапазоне (256), то у вас есть простой кладж:

>>> compressed = valueFromSqlColumn.encode('latin1')
>>> compressed
'x\x9cK\x04\x00\x00b\x00b'
>>> zlib.decompress(compressed)
'a'

Почему это работает, так это то, что кодирование/декодирование Latin1 не меняет порядковый номер. Вы можете восстановить исходное сжатое значение:

>>> compressed2 = ''.join(chr(ord(uc)) for uc in valueFromSqlColumn)
>>> compressed2
'x\x9cK\x04\x00\x00b\x00b'
>>> compressed2 == compressed
True

если вы думаете, что использование .encode('latin1') слишком похоже на вуду.

Если описанное выше не работает (т. е. некоторые порядковые номера не находятся в диапазоне (256)), вам потребуется создать небольшой исполняемый скрипт, который показывает точно и воспроизводимо, как вы сжимаете, вставляя в базу данных и извлечение из базы данных... посыпьте свой код большим количеством print "variable", repr(variable), чтобы вы могли видеть, что происходит.

person John Machin    schedule 24.10.2009
comment
@kaiser.se: У вас есть интересные определения точно и одинаково. @anonymous_driveby_downvoter: хочешь оставить объяснение? - person John Machin; 26.10.2009