Почему 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. поднимает a 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)

Ответ 1

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

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

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

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

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

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

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

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

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

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

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

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

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

Ответ 2

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

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

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