Почему Python .decode('cp037') не работает с определенным двоичным массивом?

При распечатке результатов запроса DB2 я получаю следующую ошибку в столбце «F00002», который представляет собой двоичный массив.

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

Я использую следующую строку:

print result[2].decode('cp037')

... так же, как я делаю первые два столбца, где один и тот же код работает нормально. Почему это не работает в третьем столбце и каково правильное декодирование/кодирование?

введите здесь описание изображениявведите здесь описание изображения


person user1645914    schedule 11.12.2013    source источник
comment
Что у вас есть в этих двоичных файлах и почему вы должны печатать их в виде текста?   -  person Paulo Bu    schedule 12.12.2013
comment
Когда вы спрашиваете, что такое правильное декодирование/кодирование, как вы ожидаете, что кто-то это узнает? Может быть, если бы вы показали нам фактические байты, мы могли бы догадаться… но поскольку нам нечего делать, кроме того факта, что это 245 байтов ДВОИЧНЫХ данных, которые мы не можем видеть, нам не с чем гадать.   -  person abarnert    schedule 12.12.2013
comment
@PauloBu: это информация об инвентаризации из db2 в системе as/400. Мне нужен текст, чтобы я мог передавать информацию в онлайн-базу данных mysql.   -  person user1645914    schedule 12.12.2013
comment
@abarnert: Вот что выводит, если просто напечатать результат[2] как есть — i.imgur.com/ kT5cJMW.jpg ... результат[2] = F00002 из запроса 'SELECT F00001, K00001, F00002 FROM QS36F.SH.ITEM' db2.   -  person user1645914    schedule 12.12.2013
comment
@user1645914 user1645914 вы знаете, какую кодировку использует db2 для столбцов такого типа?   -  person Paulo Bu    schedule 12.12.2013
comment
@ user1645914: Я думаю, вы приняли неправильный ответ. Если print type(result[2]) дает вам bytearray (как вы сказали в комментарии), то проблема не в том, что result[2] уже является Unicode, это просто проблема с распечаткой правильно декодированного результата.   -  person abarnert    schedule 12.12.2013
comment
@PauloBu: db2 не выполняет никакого кодирования столбцов BINARY; в этом весь смысл. Но то, что на самом деле в этих столбцах, выглядит как текст, закодированный как EBCDIC/cp037, и это то, что OP пытается восстановить.   -  person abarnert    schedule 12.12.2013
comment
@user1645914: По линии вопроса Абарнерта (в его ответе и комментариях), что вы получаете, когда print repr(result[2])?   -  person John Y    schedule 13.12.2013
comment
@JohnY Я получаю кучу байтовых массивов: pastebin.com/raw.php?i=gvmcRZma< /а>   -  person user1645914    schedule 13.12.2013


Ответы (3)


Обратите внимание, что ошибка связана с кодированием в ASCII, а не с декодированием из cp307. Но вы не просите его где-либо кодировать, так почему же это происходит?

Что ж, на самом деле есть два возможных места, где что-то может пойти не так, и мы не можем знать, в каком из них, без вашей помощи.


Во-первых, если ваш result[2] уже является объектом unicode, вызов decode('cp037') для него сначала попытается encode с sys.getdefaultencoding(), который обычно равен 'ascii', чтобы было что декодировать. Таким образом, вместо того, чтобы получить сообщение об ошибке «Эй, бозо, я уже декодирован», вы получаете сообщение об ошибке кодирования в ASCII. (Это может показаться очень глупым, но это полезно для нескольких кодеков, которые могут декодировать unicode->unicode или unicode->str, таких как ROT13 и quoted-printable.)

Если это ваша проблема, решение состоит в том, чтобы не звонить decode. Вероятно, вы уже расшифровали данные где-то на пути к этому моменту, так что не пытайтесь делать это снова. (Если вы декодировали его неправильно, вам нужно выяснить, где вы его декодировали, и исправить это, чтобы сделать это правильно; повторное декодирование уже не поможет.)


Во-вторых, передача строки Unicode в print автоматически попытается encode использовать (в зависимости от вашей версии Python) либо sys.getdefaultencoding(), либо sys.stdout.encoding. Если Python не смог угадать правильную кодировку для вашей консоли (довольно распространено в Windows) или если вы перенаправляете стандартный вывод вашего скрипта в файл вместо вывода на консоль (это означает, что Python не может угадать правильную кодировку) , вы можете получить 'ascii' даже в sys.stdout.encoding.

Если это ваша проблема, вы должны явно указать правильную кодировку для вашей консоли (если вам повезет, она будет в sys.stdout.encoding) или кодировку, которую вы хотите для текстового файла, на который вы перенаправляете (вероятно, 'utf-8', но это до вам), и явно encode все, что вы print.


Итак, как узнать, какой из них он?

Простой. print type(result[2]) и посмотреть, unicode или str. Или разбейте его на две части: x = result[2].decode('cp037'), а затем print x и посмотрите, какой из двух повышает. Или запустить в отладчике. У вас есть всевозможные варианты отладки, но вы должны что-то делать.

Конечно, также возможно, что, как только вы исправите первое, вы сразу же столкнетесь со вторым. Но теперь вы знаете, как с этим бороться.


Также обратите внимание, что cp037 — это EBCDIC, одна из немногих известных Python кодировок, которая не совместима с ASCII. На самом деле '\xe3' — это EBCDIC для буквы T.

person abarnert    schedule 11.12.2013
comment
тип печати (результат [2]) = ‹тип 'bytearray'› - person user1645914; 12.12.2013
comment
print type(result[2].decode('cp037')) = ‹type 'unicode'› (конечно) - person user1645914; 12.12.2013
comment
IBM i (AS/400, iSeries и т. д.) изначально имеет формат EBCDIC, поэтому я использую decode('cp037'). - person user1645914; 12.12.2013
comment
@ user1645914: Итак, это не первая ошибка (decode, как объяснил alko), а часть print. Вы читали Unicode HOWTO? Если вы используете Windows и Python 2.7, а ваша кодовая страница OEM для Windows является надмножеством ASCII, но не UTF-8, скорее всего, ответ будет заключаться в добавлении .encode(<something>) к каждому оператору print, где <something> зависит от кода OEM. страница есть. - person abarnert; 12.12.2013
comment
@ user1645914: Да, я предположил, исходя из того, что (а) вы использовали DB2 в 2013 году и (б) у вас были данные EBCDIC в 2013 году, что было задействовано какое-то большое железо IBM… В любом случае, похоже, что ваш драйвер DB2 не декодирует или перекодировать для вас, так что вы делаете эту часть правильно; у вас просто более общая консоль Windows отстой в проблеме Python 2.x. - person abarnert; 12.12.2013
comment
Я не в восторге от использования архаичных технологий, но работа более чем окупается. Я просто сталкиваюсь с грубыми ошибками. И да, одна рабочая станция — эта — использует Windows XP :о). Х-МФ-П... - person user1645914; 12.12.2013
comment
@ user1645914: Честно говоря, я бы предпочел иметь дело с EBCDIC, чем с производными Latin-1. (Когда вы делаете ошибку в кодировке, EBCDIC делает невозможным промах, вместо того, чтобы позволить пройти всем вашим тестам и через несколько месяцев после выпуска обнаружить, что он взрывается, когда кто-то набирает символ евро, потому что вы предположили, что CP1252 == Latin-1.) Конечно. мир, полностью основанный на UTF-8, был бы еще лучше, но до этого еще далеко (в основном благодаря Microsoft, но также и нескольким японским производителям). - person abarnert; 12.12.2013
comment
@abarnert: я думаю, что в данном случае дело не столько в том, что консоль Windows отстой, сколько в том, что в ней смешаны настоящие нетекстовые данные. (Смотрите мой ответ.) - person John Y; 13.12.2013

Кажется, что ваш result[2] уже юникод:

>>> u'\xe3'.decode('cp037')
Traceback (most recent call last):
   ...
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe3' in position 0: ordinal not in range(128)
>>> u'\xe3'.encode('cp037')
'F'

На самом деле, как указал @abarnert в комментариях, в python 2.x decode, вызываемый для объекта unicode, выполняется в два этапа:

  • кодирование в строку с sys.getdefaultencoding(),
  • затем декодирование обратно в юникод

т. е. ваше утверждение переводится как:

>>> import sys
>>> sys.getdefaultencoding()
'ascii'
>>> u'\xe3'.encode('ascii').decode('cp037')

и вы получаете ошибку из первой части выражения, u'\xe3'.encode('ascii')

person alko    schedule 11.12.2013
comment
Стоит отметить, что в Python 2.x вы можете вызывать decode для объекта Unicode, просто это очень глупо — он автоматически кодирует его, используя sys.getdefaultencoding() (обычно это 'ascii'), поэтому может затем decode результат с указанным набором символов. Вот почему ошибка такая загадочная. - person abarnert; 12.12.2013
comment
Еще веселее, потому что он передает его print, на самом деле это больше похоже на u'\xe3'.encode('ascii').decode('cp037').encode('ascii')… Но это предполагает, что result[2] на самом деле является Unicode, чего мы не знаем; вполне возможно, что он делает 'F'.decode('cp037').encode('ascii') - person abarnert; 12.12.2013

Хорошо, как установил @abarnert, у вас действительно нет проблемы с Unicode как таковой. Юникод входит в картинку только при попытке print. Посмотрев на ваши данные, я вижу, что на самом деле в там, но и произвольные двоичные данные. Данные определенно кажутся столбчатыми, поэтому у нас, вероятно, есть набор подполей, упакованных в поле под названием F00002 в вашем примере. Программисты RPG назвали бы это структурой данных; это похоже на C struct.

Столбцы F00001 и K00001, вероятно, работали нормально, потому что они содержат только символьные данные EBCDIC.

Поэтому, если вы хотите извлечь полные данные из F00002, вам придется выяснить (через документацию или у кого-то, кто обладает знаниями), что это за подполя. Как правило, после того, как вы обнаружите это, вы можете просто использовать модуль Python struct для быстрой и простой распаковки данных, но, поскольку данные поступают из IBM i, вы можете столкнуться с преобразованием его собственных типов данных в типы Python. (Наиболее распространенным из них является упакованный десятичный формат для числовых данных.)

На данный момент вы все еще можете извлекать части символов F00002 путем декодирования, как и раньше, но затем явно выбирая новую кодировку, которая работает с вашим выводом (отображением или файлом), как предложил @abarnert. Я рекомендую записывать значения в файл, используя result[2].decode('cp037').encode('utf-8') (что создаст кучу явно неудобочитаемых данных, вкрапленных в текст; вы можете использовать это как есть, или вы можете использовать его, по крайней мере, для сказать вам, где находятся части текста для дальнейшей обработки).


Изменить:

У нас нет времени делать за вас всю вашу работу и исследования. Вещи, которые вам нужно просто прочитать и поработать над собой:

  • Упакованный десятичный формат IBM (ускоренный курс: каждая цифра занимает 4 бита с использованием основного шестнадцатеричного; с дополнительными 4 битами справа для знака, то есть «F» для положительного и «D» для отрицательного; все это дополнено нулями слева, если необходимо заполнить целое число байтов; десятичный разряд подразумевается)
  • Зонированный десятичный формат IBM (ускоренный курс: каждая цифра занимает 1 байт и идентична представлению соответствующего символа в формате EBCDIC; за исключением того, что в самой правой цифре старшие 4 бита используются для знака, «F» для положительного и «D» ' для отрицательного; подразумевается десятичный разряд)
  • Модуль Python struct (не обрабатывает вышеуказанные типы автоматически; вы должны использовать необработанные байты для всего (тип 's') и обрабатывать по мере необходимости)
  • Возможно, вы почерпнете некоторые идеи (и код) для обработки упакованных и зонированных десятичных знаков IBM из дополнительного модуля api2. для iSeriesPython 2.7 (в частности, обратите внимание на класс iSeriesStruct, который является подклассом struct.Struct, учитывая, что весь модуль предназначен для работы на iSeries с использованием iSeriesPython, и, таким образом, не обязательно использовать как есть из обычного Python, взаимодействующего с iSeries через pyodbc).
person John Y    schedule 13.12.2013
comment
Вот как выглядит мой файл QS36F.SH.ITEM с 65 подполями: i.imgur.com/ XDduyJy.jpg i.imgur.com/8Cuiqui.jpg i.imgur.com/GDF6DmY.jpg i.imgur.com/yiHr1nx.jpg i.imgur.com/CbwNj91.jpg i.imgur.com/YgZbUrO.jpg ... как мне использовать struct для быстрой и простой распаковки данных? - person user1645914; 13.12.2013
comment
@user1645914: Вы читали документы struct? Это довольно просто: если все поля имеют фиксированную ширину, вы просто пишете строку формата, в которой перечисляются все типы и ширины полей по порядку, а затем f00002_fields = struct.unpack(F00002_FORMAT_STRING, result[2]). Если они имеют переменную ширину, вам нужно unpack_from по одному (например, сначала распаковать ширину, а затем использовать ее как количество символов для распаковки в виде строки). В любом случае вы получите строки EBCDIC (вместе с целыми числами и т. д.), которые вам нужно .decode. - person abarnert; 14.12.2013
comment
@ user1645914: Но просто увидеть список столбцов недостаточно, чтобы узнать, каковы типы и ширина полей (за исключением утомительного обратного проектирования). У вас должен быть какой-то способ получить определение этого отчета или что-то в этом роде, которое скажет вам, что, например, поле 0 является 16-битным целым числом без знака (он может не сказать вам, что это, скажем, с обратным порядком байтов, но что вы можете догадаться, и это должно быть верно для всех), поле 3 представляет собой 25-символьную строку фиксированной ширины и т. д. Отсюда будет легко писать. - person abarnert; 14.12.2013
comment
@abarnert: Маловероятно, что какое-либо из полей имеет тип unsigned int или тому подобное. Это не тот путь, по которому обычно работает DB2. Особенно в среде System/36 (библиотека QS36F). В основном они будут упакованы десятичными или зонными. Таким образом, struct не будет предлагать немедленную помощь для числовых значений, кроме как просто нарезать bytearray на str (строки байтов) соответствующего размера. Это то, что я имел в виду, говоря о необходимости преобразования собственных типов i в Python. - person John Y; 14.12.2013
comment
@ user1645914: На снимках экрана показаны несколько ведущих столбцов (EACT1 и EITEM), которые не являются подполями F00002. Возможно, это F00001 и K00001? Я не знаю. Но вам нужно узнать общий размер (в байтах) и количество знаков после запятой для каждого из числовых подполей. После того, как вы преодолеете это, вы можете беспокоиться о том, как перевести упакованные десятичные данные. (Быстрый взгляд на необработанные данные bytearray выглядит так, будто все это либо текст EBCDIC, либо упакованное десятичное число.) - person John Y; 14.12.2013
comment
@JohnY: я бы все равно сделал это, взяв одну из расширяемых повторных реализаций struct на чистом Python вне PyPI (например, netstruct, или тот, который делает битовые поля, название которых я не могу вспомнить) и добавление поддержки упакованного десятичного числа. Но ваша основная мысль является ключевой — первый шаг к анализу данных — это выяснить, что это за данные, в идеале — прочитать их где-нибудь, а не реконструировать. - person abarnert; 16.12.2013
comment
Я все еще застрял. Таблица SH.ITEM состоит из трех столбцов: F00001 (EACT1), K00001 (EITEM) и F00002 (69 подполей). Я декодирую первые два через .decode('cp037') без проблем. Я воспользовался советом @abarnert, но не могу понять структуру. Вот что я пробовал: pastebin.com/raw.php?i=BPkugbqg . Я собрал ссылку на поле данных для SH.ITEM здесь: pastebin.com/raw.php?i =ruXLaGXR и поместите сюда 100 строк его данных: pastebin.com/raw.php ?i=wg0SU0tz. Когда F00002 декодируется, эти более длинные текстовые поля имеют разную длину, что делает синтаксический анализ довольно сложным. - person user1645914; 17.12.2013
comment
@user1645914: Если эти поля на самом деле являются 32-битным целым числом с собственным порядком байтов, строкой из 25 символов, собственным числом с плавающей запятой IEEE и т. д., этот формат struct не будет работать. И, как объяснил Джон Y в комментариях выше, очень маловероятно, что ваши числовые поля являются 32-битными целыми числами с собственным порядком байтов и т. д. - person abarnert; 17.12.2013
comment
@ user1645914: Поскольку у вас есть определения (под) полей, на данный момент у вас есть все необходимые инструменты. Смотрите мой отредактированный ответ. - person John Y; 17.12.2013