1

Я работаю над чтением из файла dbf произвольной кодировки.

filename = request.FILES['file']
file_in_memory = StringIO(filename.read())
zip_file = ZipFile(file_in_memory, 'r')
clouddbf = StringIO(zip_file.read(name, 'rb'))
file_import = sh.Reader(dbf=clouddbf)
fields = file_import.fields
>>> fields
[('DeletionFlag', 'C', 1, 0), ['id', 'C', 50, 0], ['\xc8\xed\xe8\xf6\xe8\xe0\xf2\xee\xf0_', 'C', 254, 0], ['\xc3\xee\xe4_\xe2\xe2\xee\xe4\xe0_', 'C', 254, 0]]

В таблице содержится кириллица, и когда я пытаюсь вывести на экран заголовок столбца (столбец называется Инициатор_), сталкиваюсь со странной проблемой.

>>> print chardet.detect(fields[i+1][0])['encoding']
MacCyrillic
>>> print fields[i+1][0]
╚эшЎшрЄюЁ_ # здесь кодировка cp866 (кодировка командной строки)
>>> print file_import.fields[i+1][0].decode('cp1251')
Инициатор_
>>> print fields[i+1][0].decode(chardet.detect(fields[i+1][0])['encoding'], errors='ignore')
Python27\Lib\encodings\cp866.py in encode
  12.         return codecs.charmap_encode(input,errors,encoding_map)
Exception Value: 'charmap' codec can't encode character u'\xbb' in position 0: character maps to <undefined>

В документации сказано, что если errors='ignore', то ошибочные символы пропускаются, а не приводят к ошибке.

Вопрос: почему выходит ошибка?

insolor
  • 49,104
Mae
  • 836
  • 1
  • 8
  • 23
  • ответ явно говорит, что невозможно узнать кодировку (chardet может только гадать). Ответ явно показывает откуда кракозябры берутся. Показывает как от UnicodeEncodeError избавится. От части заблуждений должен вас избавить, что должно позволить более конкретный вопрос задать. – jfs Dec 27 '16 at 12:48
  • Если у вас fields[i+1][0] имеет тип unicode, то его в принципе нельзя декодировать, потому что он уже и так декодирован в юникод :) Кого, откуда и как вы читаете то, что у вас получается в переменной fields[i+1][0]? – andreymal Dec 27 '16 at 12:53
  • @jfs Там говорится, что можно использовать chardet, например. Может быть, есть ещё способы... Не надо закрывать мой вопрос. Не нравится - заминусуйте. В "возможном дубликате" нет ответа на него, а мне нужен ответ. – Mae Dec 27 '16 at 12:57
  • @Mae в вопросе не написано, откуда вы взяли этот «заголовок поля в таблице dbf». Вот напишите. И откуда взяли filename тоже напишите — важно всё. – andreymal Dec 27 '16 at 13:03
  • @Mae вы хотите чтобы я "невозможно определить кодировку файла только по его содержимому однозначно со 100% гарантией" в качестве ответа добавил—это вам или кому-то ещё как то поможет? Если человек нашёл ваш вопрос потому что у него проблемы с печатью в консоль (показываются кракозябры, вылетает UnicodeEncodeError), то ответ "невозможно определить кодировку" не поможет, а поможет ответ, на который я сослался. Не торопитесь, попробуйте код из ответа. Попытайтесь понять что такое Unicode, а что байты. Затем отредактируйте вопрос, чтобы убрать кракозябры и UnicodeEncodeError. – jfs Dec 27 '16 at 13:05
  • @jfs Конкретно от вас я ничего не требую. Было бы здорово, если бы знающий человек написал "попробуйте вместо chardet использовать xxx". И ещё я хочу разобраться, почему не срабатывает errors='ignore'. Вот скажите, есть в том вопросе этот момент? Я не вижу. – Mae Dec 27 '16 at 13:10
  • @andreymal Готово. – Mae Dec 27 '16 at 13:17
  • Если у вас вопрос про `errors="ignore", то и спрашивайте конкретно про это: укажите явно ожидаемый вывод, и что вместо этого получаете. Используйте более специфичный заголовок, который явно errors ignore упоминает. О "Вместо chardet использовать xxx"—что вы не понимаете в слове "невозможно"? В общем случае, невозможно узнать кодировку. – jfs Dec 27 '16 at 13:18
  • @jfs Минут 20 назад заголовок поменялся... – Mae Dec 27 '16 at 13:19
  • Это вам никак правильную encoding не поможет найти. В чём вы видите ошибку с errors="ignore"? Напишите в форме: «Y = X.decode(encoding, errors='ignores') получаю Y, а хочу Z (покажите print repr(X), repr(Y), repr(Z))». – jfs Dec 27 '16 at 13:47

1 Answers1

3

В документации сказано, что если errors='ignore', то ошибочные символы пропускаются, а не приводят к ошибке.

Ошибка ('charmap' codec can't encode character u'\xbb'...) возникает при печати Юникода уже после того как .decode(..., errors='ignore') вызов уже завершился. Другими словами, errors='ignore' не вызывает ошибку. errors='ignore' не пытается угадать кодировку—она игнорирует ошибки при декодировании, используя кодировку, которую вы явно указали в вызове .decode(). errors='ignore' работает как ожидается.

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

Чтобы устранить ошибку при печати в консоль, установите win-unicode-console пакет, как явно указано в ответе, на который я выше сослался. Это уберёт ошибку при печати, но вы просто кракозябры начнёте видеть, так как неправильная кодировка используется.

Чтобы непредставимые символы не возникали (для строк, которые можно было до этого в консоль выводить), используйте правильную кодировку. Так как один и тот же байт может декодироваться в разные символы разными кодировками. Если вы неправильную кодировку используете, то это к кракозябрам ведёт, как проиллюстрировано в упомянутом ответе

Имея последовательность байт (к примеру, файл на диске), в общем случае, невозможно со 100% гарантией найти правильную кодировку. К примеру:

>>> import chardet # pip install chardet
>>> chardet.detect(b'\xc3\xee\xe4_\xe2\xe2\xee\xe4\xe0_')
{'confidence': 0.8532996928462425, 'encoding': 'windows-1251'}

chardet может только гадать (как явно сказано в дважды упомянутом ответе). Нет никакой гарантии, что он правильную кодировку возвращает.

Значение кодировки зависит от конкретного случая (источника данных). В вопросе недостаточно данных, чтобы понять почему именно cp1251 (ANSI codepage на русской Windows) является правильной кодировкой в этом случае.

jfs
  • 52,361
  • Спасибо за ответ, @jfs. Непонятно только, зачем вы наполняете ответ лишними подробностями. Например, я не спрашиваю, откуда крокозябры. Я не утверждаю, что chardet гарантирует кодировку, а вы с каким-то упорством напираете на "упомянутый" ответ. В любом случае, вы мне помогли, спасибо. – Mae Dec 27 '16 at 14:58
  • @Mae я ответ не только для вас пишу. Цель сайта, чтобы люди с похожей проблемой могли решение найти. На большом Stack Overflow подавляющая часть посетителей из гугла (в 10-20 раз больше других источников), поэтому ответ он не столько для автора вопроса, сколько для этих будущих посетителей из поисковика. – jfs Dec 27 '16 at 15:11