Почему sys.getdefaultencoding() отличается от sys.stdout.encoding и как это нарушает строки Unicode?

Я провел несколько гневных часов в поисках проблемы со строками Unicode, которые были разбиты на что-то, что Python (2.7) скрывает от меня, и я до сих пор не понимаю. Во-первых, я пытался последовательно использовать u".." строки в своем коде, но это привело к печально известному UnicodeEncodeError. Я пробовал использовать .encode('utf8'), но это тоже не помогло. В конце концов, оказалось, что я не должен использовать ни то, ни другое, и все работает автоматически. Тем не менее, я (здесь я должен отдать должное другу, который помог мне) заметил что-то странное, стуча головой о стену. sys.getdefaultencoding() возвращает ascii, а sys.stdout.encoding возвращает UTF-8. 1. в приведенном ниже коде отлично работает без каких-либо модификаций sys и 2. вызывает UnicodeEncodeError. Если я изменю системную кодировку по умолчанию на reload(sys).setdefaultencoding("utf8"), то 2. работает нормально. Мой вопрос заключается в том, почему две переменные кодирования отличаются в первую очередь и как мне удается использовать неправильную кодировку в этом простом фрагменте кода? Пожалуйста, не посылайте меня к Unicode HOWTO, я прочитал это очевидно, в десятках вопросов о UnicodeEncodeError.

#  -*- coding: utf-8 -*-
import sys


class Token:
    def __init__(self, string, final=False):
        self.value = string
        self.final = final

    def __str__(self):
        return self.value

    def __repr__(self):
        return self.value

print(sys.getdefaultencoding())
print(sys.stdout.encoding)

# 1.
myString = "I need 20 000€."
tok = Token(myString)
print(tok)

reload(sys).setdefaultencoding("utf8")

# 2.
myString = u"I need 20 000€."
tok = Token(myString)
print(tok)

person Aleksandar Savkov    schedule 20.03.2013    source источник
comment
Мой вопрос не о решении проблемы, а о ее причинах. Я знаю, как ее решить, и я знаю, что ее нет в Python 3.0. То, о чем вы спрашиваете, не имеет значения.   -  person Aleksandar Savkov    schedule 20.03.2013


Ответы (2)


Мой вопрос в том, почему две переменные кодирования различны в первую очередь

Они служат разным целям.

sys.stdout.encoding должна быть кодировкой, которую ваш терминал использует для интерпретации текста, иначе вы можете получить моджибаке на выходе. Это может быть utf-8 в одной среде, cp437 в другой и т.д.

sys.getdefaultencoding() используется в Python 2 для неявных преобразований (когда кодировка не задана явно), т. е. Python 2 может смешивать строки байтов, содержащие только ascii, и строки Unicode, например, xml.etree.ElementTree сохраняет текст в диапазоне ascii как строки байтов или json.dumps() возвращает строку байтов только для ascii вместо Unicode в Python 2 - возможно, из-за производительности - байты были дешевле, чем Unicode для представления символов ascii. Неявные преобразования запрещены в Python 3.

sys.getdefaultencoding() всегда 'ascii' во всех системах в Python 2, если только вы не переопределите его, чего делать не следует, иначе это может скрыть ошибки, и ваши данные могут быть легко повреждены из-за неявных преобразований с использованием, возможно, неправильной кодировки для данных.

Кстати, есть еще одна распространенная кодировка sys.getfilesystemencoding(), которая может отличаться от двух. sys.getfilesystemencoding() должна быть кодировкой, используемой для кодирования данных ОС (имен файлов, аргументов командной строки, переменных среды).

Кодировка исходного кода, объявленная с помощью # -*- coding: utf-8 -*-, может отличаться от всех уже упомянутых кодировок.

Естественно, если вы читаете данные из файла, сети; он может использовать кодировки символов, отличные от приведенных выше, например, если файл, созданный в блокноте, сохранен с использованием кодировки Windows ANSI, такой как cp1252, то в другой системе все стандартные кодировки могут отличаться от нее.

Дело в том, что может быть несколько кодировок по причинам, не связанным с Python, и чтобы избежать головной боли, используйте Unicode для представления текста: как можно скорее преобразуйте закодированный текст в Unicode на ввода и кодировать его в байты (возможно, используя другую кодировку) как можно позже на выходе — это так называемая концепция сэндвич Unicode.

как мне удается использовать неправильную кодировку в этом простом фрагменте кода?

  1. Ваш первый пример кода не в порядке. Вы используете литеральные символы, отличные от ascii, в строке байтов на Python 2, чего делать не следует. Используйте литералы строк байтов только для двоичных данных (или так называемых собственных строк, если необходимо). Код может создавать моджибаке, такой как I need 20 000Γé¼. (обратите внимание на шум символов), если вы запускаете его с помощью Python 2 в любой среде, которая не использует кодировку, совместимую с utf-8, например в консоли Windows.

  2. Второй пример кода в порядке, если предположить, что reload(sys) не является его частью. Если вы не хотите ставить перед всеми строковыми литералами префикс u''; вы могли бы использовать from __future__ import unicode_literals

Ваша фактическая проблема — ошибка UnicodeEncodeError, а reload(sys) — неправильное решение!
Правильное решение — правильно настроить локаль в POSIX (LANG , LC_CTYPE) или установите PYTHONIOENCODING envvar, если вывод перенаправляется в канал/файл, или установите win-unicode-console для печати Unicode на консоль Windows .

person jfs    schedule 16.11.2015

Я заметил такое же поведение некоторого стандартного кода (библиотека почтальона). Спасибо за ваш анализ, это помогло мне сэкономить время. :-) Проблема точно такая же. Моя система использует sys.getdefaultencoding() и получает ascii, что не подходит для обработки списка из 1000 имен в кодировке UTF-8.

Существует несоответствие между stdin/stdout и даже кодировкой файловой системы (utf-8), с одной стороны, и «кодировкой по умолчанию», с другой (ascii). Этот поток: Как печатать в кодировке UTF-8 текст на консоль в Python ‹ 3? кажется, указывает на то, что он хорошо известен и Изменение кодировки Python по умолчанию? содержит некоторое указание на то, что более однородное (например, «utf-8 везде») нарушит другие вещи, такие как реализация хэша.

По этой причине также непросто изменить кодировку по умолчанию. (См. http://blog.ianbicking.org/illusive-setdefaultencoding.html для различными способами.) Он удален из экземпляра sys в файле site.py.

person Dirk    schedule 14.09.2013