UnicodeEncodeError: кодек "charmap" не может кодировать - карты символов для <undefined>, функция печати

Я пишу программу Python (Python 3.3) для отправки некоторых данных на веб-страницу с использованием метода POST. В основном для процесса отладки я получаю результат страницы и отображаю ее на экране с помощью функции print().

Код выглядит следующим образом:

conn.request("POST", resource, params, headers)
response = conn.getresponse()
print(response.status, response.reason)
data = response.read()
print(data.decode('utf-8'));

метод HTTPResponse .read() возвращает элемент bytes, кодирующий страницу (который является хорошо сформированным документом UTF-8). Это выглядело нормально, пока я не прекратил использовать IDLE GUI для Windows и вместо этого использовал консоль Windows. На возвращаемой странице есть символ U + 2014 (em-dash), который функция печати хорошо переносит в графическом интерфейсе Windows (я предполагаю, что код страницы 1252), но не находится в консоли Windows (кодовая страница 850). Учитывая поведение strict по умолчанию, я получаю следующую ошибку:

UnicodeEncodeError: 'charmap' codec can't encode character '\u2014' in position 10248: character maps to <undefined>

Я мог бы исправить это с помощью этого довольно уродливого кода:

print(data.decode('utf-8').encode('cp850','replace').decode('cp850'))

Теперь он заменяет оскорбительный символ "-" на ?. Не идеальный случай (дефис должен быть лучшей заменой), но достаточно хорош для моей цели.

В моем решении есть несколько вещей, которые мне не нравятся.

  • Код уродливый со всем этим декодированием, кодированием и декодированием.
  • Он решает проблему только для этого случая. Если я переношу программу на систему с использованием какой-либо другой кодировки (latin-1, cp437, back to cp1252 и т.д.), Она должна распознать целевую кодировку. Это не. (например, при повторном использовании IDLE GUI, emdash также теряется, чего раньше не было)
  • Было бы лучше, если бы emdash переводили в дефис вместо опроса.

Проблема не в emdash (я могу придумать несколько способов решить эту проблему), но мне нужно написать надежный код. Я загружаю страницу данными из базы данных и данные могут возвращаться. Я могу предвидеть многие другие конфликтующие случаи: "Á" U + 00c1 (что возможно в моей базе данных) может перевести на CP-850 (DOS/Windows Console encodign для западноевропейских языков), но не в CP-437 (кодировка для США Английский, который по умолчанию используется во многих установках Windows).

Итак, вопрос:

Есть ли более приятное решение, которое делает мой код агностиком из кодирования выходного интерфейса?

Ответ 1

Я вижу три решения:

  • Измените выходную кодировку, чтобы она всегда выводила UTF-8. См. Установка правильной кодировки при отправке stdout в Python, но я не мог заставить этот пример работать.

  • В следующем примере код выводит информацию о вашей целевой кодировке.

    # -*- coding: utf-8 -*-
    import sys
    
    print sys.stdout.encoding
    print u"Stöcker".encode(sys.stdout.encoding, errors='replace')
    print u"Стоескер".encode(sys.stdout.encoding, errors='replace')
    

    Этот пример правильно заменяет любой непечатаемый символ в моем имени вопросительным знаком.

    Если вы создаете пользовательскую функцию печати, например. называемый myprint, используя эти механизмы для правильного кодирования вывода, вы можете просто заменить печать myprint необходимым, не делая весь код выглядящим уродливым.

  • Reset кодирование вывода во всем мире в начале программного обеспечения:

    На странице http://www.macfreek.nl/memory/Encoding_of_Python_stdout есть хорошее резюме, что нужно сделать, чтобы изменить кодировку вывода. Особенно интересен раздел "Обтекатель StreamWriter вокруг Stdout". По сути, он говорит об изменении функции кодирования ввода-вывода следующим образом:

    В Python 2:

    if sys.stdout.encoding != 'cp850':
      sys.stdout = codecs.getwriter('cp850')(sys.stdout, 'strict')
    if sys.stderr.encoding != 'cp850':
      sys.stderr = codecs.getwriter('cp850')(sys.stderr, 'strict')
    

    В Python 3:

    if sys.stdout.encoding != 'cp850':
      sys.stdout = codecs.getwriter('cp850')(sys.stdout.buffer, 'strict')
    if sys.stderr.encoding != 'cp850':
      sys.stderr = codecs.getwriter('cp850')(sys.stderr.buffer, 'strict')
    

    Если в CGI выводится HTML-код, вы можете заменить "strict" на "xmlcharrefreplace", чтобы получить HTML-кодированные теги для непечатаемых символов.

    Не стесняйтесь модифицировать подходы, устанавливая разные кодировки,... Обратите внимание, что он по-прежнему не работает для вывода не указанных данных. Поэтому любые данные, ввод, тексты должны быть правильно преобразованы в unicode:

    # -*- coding: utf-8 -*-
    import sys
    import codecs
    sys.stdout = codecs.getwriter("iso-8859-1")(sys.stdout, 'xmlcharrefreplace')
    print u"Stöcker"                # works
    print "Stöcker".decode("utf-8") # works
    print "Stöcker"                 # fails
    

Ответ 2

Основываясь на ответе Дирка Штелькера, здесь используется аккуратная функция обертки для функции печати Python 3. Используйте его так же, как вы использовали бы печать.

В качестве дополнительного бонуса, по сравнению с другими ответами, это не будет печатать ваш текст как bytearray ('b "content" '), а как обычные строки ( "контент" ) из-за последнего этапа декодирования.

def uprint(*objects, sep=' ', end='\n', file=sys.stdout):
    enc = file.encoding
    if enc == 'UTF-8':
        print(*objects, sep=sep, end=end, file=file)
    else:
        f = lambda obj: str(obj).encode(enc, errors='backslashreplace').decode(enc)
        print(*map(f, objects), sep=sep, end=end, file=file)

uprint('foo')
uprint(u'Antonín Dvořák')
uprint('foo', 'bar', u'Antonín Dvořák')

Ответ 3

Для целей отладки вы можете использовать print(repr(data)).

Чтобы отобразить текст, всегда печатайте Юникод. Не перекодируйте кодировку символов вашей среды, например cp850 внутри script. Чтобы декодировать ответ HTTP, см. Хороший способ получить кодировку/кодировку ответа HTTP в Python.

Чтобы печатать Unicode в консоли Windows, вы можете использовать win-unicode-console пакет.

Ответ 4

Я углубился в это и нашел лучшие решения здесь.

http://blog.notdot.net/2010/07/Getting-unicode-right-in-Python

В моем случае я решил "UnicodeEncodeError: кодек" charmap "не может кодировать символ"

исходный код:

print("Process lines, file_name command_line %s\n"% command_line))

Новый код:

print("Process lines, file_name command_line %s\n"% command_line.encode('utf-8'))  

Ответ 5

Если вы используете командную строку Windows для печати данных, вы должны использовать

chcp 65001

Это сработало для меня!

Ответ 6

Если вы используете Python 3.6 (возможно, 3.5 или новее), это больше не дает мне этой ошибки. У меня была аналогичная проблема, потому что я использовал v3.4, но после того, как я удалил и переустановил, он исчез.