Почему Python печатает символы Unicode, когда кодировка по умолчанию - ASCII?

Из оболочки Python 2.6:

>>> import sys
>>> print sys.getdefaultencoding()
ascii
>>> print u'\xe9'
é
>>> 

Я ожидал, что у вас будет какая-то тарабарщина или ошибка после утверждения print, так как символ "é" не является частью ASCII, и я не указал кодировку. Думаю, я не понимаю, что ASCII является средством кодирования по умолчанию.

ИЗМЕНИТЬ

Я переместил редактирование в раздел Ответы и принял его как предложение.

Ответ 1

Благодаря бит и кусочкам из разных ответов, я думаю, мы сможем выстроить объяснение.

Пытаясь напечатать строку unicode, u '\ xe9', Python неявно пытается кодировать эту строку, используя схему кодирования, которая в настоящее время хранится в sys.stdout.encoding. Python фактически берет этот параметр из среды, с которой он был инициирован. Если он не может найти правильную кодировку из среды, только тогда она вернется к ее по умолчанию, ASCII.

Например, я использую оболочку bash, для которой по умолчанию используется кодировка UTF-8. Если я запускаю Python из него, он выбирает и использует этот параметр:

$ python

>>> import sys
>>> print sys.stdout.encoding
UTF-8

Позвольте на мгновение выйти из оболочки Python и установить среду bash с некоторой фиктивной кодировкой:

$ export LC_CTYPE=klingon
# we should get some error message here, just ignore it.

Затем запустите оболочку python и убедитесь, что она действительно вернется к своей кодировке ascii по умолчанию.

$ python

>>> import sys
>>> print sys.stdout.encoding
ANSI_X3.4-1968

Бинго!

Если вы попытаетесь вывести некоторый символ unicode за пределы ascii, вы должны получить сообщение об ошибке

>>> print u'\xe9'
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe9' 
in position 0: ordinal not in range(128)

Позволяет выйти из Python и отказаться от оболочки bash.

Теперь мы увидим, что произойдет после вывода строк Python. Для этого мы сначала запустим оболочку bash в графическом терминале (я использую терминал Gnome), и мы установим, что терминал будет декодировать вывод с ISO-8859-1 aka latin-1 (графические терминалы обычно имеют возможность Установите кодировку символов в одном из раскрывающихся меню). Обратите внимание, что это не изменяет кодировку среды оболочки оболочки, она только изменяет способ, которым сам терминал будет декодировать выданный им вывод, немного похожий на веб-браузер. Поэтому вы можете изменить кодировку терминала независимо от среды оболочки. Затем запустите Python из оболочки и убедитесь, что для sys.stdout.encoding установлено кодирование оболочки оболочки (UTF-8 для меня):

$ python

>>> import sys

>>> print sys.stdout.encoding
UTF-8

>>> print '\xe9' # (1)
é
>>> print u'\xe9' # (2)
é
>>> print u'\xe9'.encode('latin-1') # (3)
é
>>>

(1) python выводит двоичную строку как есть, терминал принимает ее и пытается сопоставить ее значение с латинской символьной картой. В латинском-1, 0xe9 или 233 выдает символ "é" и так, что отображается терминал.

(2) python пытается неявно кодировать строку Unicode с любой схемой, установленной в настоящее время в sys.stdout.encoding, в данном случае это "UTF-8". После кодирования UTF-8 результирующая двоичная строка равна "\ xc3\xa9" (см. Последующее объяснение). Терминал принимает поток как таковой и пытается декодировать 0xc3a9 с использованием латинского-1, но латинский-1 переходит от 0 до 255 и, следовательно, только декодирует потоки по 1 байт за раз. Таким образом, декодер 0xc3a9 имеет длину 2 байта, поэтому декодер latin-1 интерпретирует его как 0xc3 (195) и 0xa9 (169) и дает 2 символа: Ã и ©.

(3) python кодирует юникодную кодовую точку u '\ xe9' (233) с латинской схемой. Выключает диапазон кодовых точек латинского-1 0-255 и указывает на тот же самый символ, что и Unicode в этом диапазоне. Таким образом, коды кода Unicode в этом диапазоне будут иметь одинаковое значение при кодировании в латинском-1. Таким образом, u '\ xe9' (233), закодированный в латинском-1, также даст двоичную строку '\ xe9'. Терминал получает это значение и пытается сопоставить его на карте символов латинского-1. Как и в случае (1), он дает "é" и то, что отображается.

Теперь изменим настройки терминальной кодировки на UTF-8 в раскрывающемся меню (например, вы измените настройки кодировки веб-браузера). Не нужно останавливать Python или перезапускать оболочку. Терминальная кодировка теперь соответствует Python. Повторите попытку печати:

>>> print '\xe9' # (4)

>>> print u'\xe9' # (5)
é
>>> print u'\xe9'.encode('latin-1') # (6)

>>>

(4) python выводит двоичную строку как есть. Терминал пытается декодировать этот поток с помощью UTF-8. Но UTF-8 не понимает значение 0xe9 (см. Последующее объяснение) и поэтому не может преобразовать его в кодовую точку юникода. Кодовая точка не найдена, символ не напечатан.

(5) python пытается неявно кодировать строку Unicode любым из них в sys.stdout.encoding. Еще "UTF-8". Результирующая двоичная строка: '\ xc3\xa9'. Терминал получает поток и пытается декодировать 0xc3a9, также используя UTF-8. Он возвращает обратное значение кода 0xe9 (233), которое на карте символов Юникода указывает на символ "é" . Терминал отображает "é" .

(6) python кодирует строку unicode с латинским-1, он дает двоичную строку с тем же значением '\ xe9'. Опять же, для терминала это почти то же самое, что и случай (4).

Выводы: - Python выводит строки non-unicode в качестве исходных данных, не учитывая его кодировку по умолчанию. Терминал просто появляется, чтобы отображать их, если его текущее кодирование соответствует данным. - Python выводит строки Unicode после их кодирования с использованием схемы, указанной в sys.stdout.encoding. Python получает эту настройку из среды оболочки. - терминал отображает вывод в соответствии с его собственными настройками кодирования. - терминальная кодировка не зависит от оболочки.


Подробнее о юникоде, UTF-8 и латинском-1:

Unicode - это, в основном, таблица символов, где некоторым клавишам (кодовым точкам) условно присвоено указание на некоторые символы. например по соглашению было принято решение, что ключ 0xe9 (233) - это значение, указывающее на символ "é" . ASCII и Unicode используют те же кодовые точки от 0 до 127, как и латинские-1 и Unicode от 0 до 255. То есть 0x41 указывает на "A" в ASCII, латинском-1 и Unicode, 0xc8 указывает на "Ü" в latin-1 и Unicode, 0xe9 указывает на "é" в латинском-1 и Unicode.

При работе с электронными устройствами кодовые точки Unicode нуждаются в эффективном способе представления в электронном виде. Это то, что схемы кодирования. Существуют различные схемы кодирования Unicode (utf7, UTF-8, UTF-16, UTF-32). Наиболее интуитивно понятным и прямолинейным подходом к кодированию было бы просто использовать значение кодовой точки в карте Юникода как его значение для его электронной формы, но Unicode в настоящее время имеет более миллиона кодовых точек, что означает, что некоторым из них требуется 3 байта выражены. Для эффективной работы с текстом сопоставление от 1 до 1 было бы непрактичным, поскольку для этого требовалось бы, чтобы все кодовые точки хранились в точно таком же объеме пространства, как минимум 3 байта на символ, независимо от их реальной потребности.

Большинство схем кодирования имеют недостатки в отношении требований к пространству, наиболее экономичные не охватывают все кодовые точки Юникода, например, ascii охватывает только первые 128, тогда как латинский-1 охватывает первые 256. Другие, которые пытаются быть более полными в конечном итоге также являются расточительными, поскольку они требуют больше байтов, чем необходимо, даже для обычных "дешевых" символов. Например, UTF-16 использует минимум 2 байта на символ, в том числе в диапазоне ascii ( "B", которому 65, по-прежнему требуется 2 байта хранения в UTF-16). UTF-32 еще более расточительный, поскольку он хранит все символы в 4 байта.

UTF-8, по-видимому, умело разрешил дилемму, со схемой, способной хранить кодовые точки с переменным количеством байтовых пространств. В рамках своей стратегии кодирования UTF-8 крутит кодовые точки с битами флагов, которые указывают (предположительно на декодеры) свои требования к пространству и их границы.

Кодирование UTF-8 кодовых точек юникода в диапазоне ascii (0-127):

0xxx xxxx  (in binary)
  • x показывает фактическое пространство, зарезервированное для "сохранения" кодовой точки во время кодирования.
  • Ведущий 0 - это флаг, который указывает декодеру UTF-8, что для этой кодовой точки потребуется только 1 байт.
  • при кодировании UTF-8 не изменяет значение кодовых точек в этом конкретном диапазоне (то есть 65, закодированное в UTF-8, также равно 65). Учитывая, что Unicode и ASCII также совместимы в одном и том же диапазоне, он, кстати, делает UTF-8 и ASCII также совместимыми в этом диапазоне.

например. Кодовая точка Unicode для "B" - это "0x42" или 0100 0010 в двоичном формате (как мы сказали, это то же самое в ASCII). После кодирования в UTF-8 он становится:

0xxx xxxx  <-- UTF-8 encoding for Unicode code points 0 to 127
*100 0010  <-- Unicode code point 0x42
0100 0010  <-- UTF-8 encoded (exactly the same)

Кодирование UTF-8 кодовых точек Юникода выше 127 (не-ascii):

110x xxxx 10xx xxxx            <-- (from 128 to 2047)
1110 xxxx 10xx xxxx 10xx xxxx  <-- (from 2048 to 65535)
  • ведущие биты 110 'указывают декодеру UTF-8 начало кодовой точки, закодированной в 2 байта, тогда как "1110" указывает 3 байта, 11110 будет указывать 4 байта и т.д.
  • внутренние биты флага "10" используются для сигнализации начала внутреннего байта.
  • снова, x отметьте пробел, где значение кодовой точки Юникода сохраняется после кодирования.

например. 'é' Кодовая точка Unicode равна 0xe9 (233).

1110 1001    <-- 0xe9

Когда UTF-8 кодирует это значение, он определяет, что значение больше 127 и меньше 2048, поэтому должно быть закодировано в 2 байта:

110x xxxx 10xx xxxx   <-- UTF-8 encoding for Unicode 128-2047
***0 0011 **10 1001   <-- 0xe9
1100 0011 1010 1001   <-- 'é' after UTF-8 encoding
C    3    A    9

Точки кода Unicode 0xe9 после кодировки UTF-8 становятся 0xc3a9. Именно так терминал получает его. Если ваш терминал настроен на декодирование строк с использованием латинского-1 (один из кодировок, не относящихся к юникоду), вы увидите Ã ©, потому что так получилось, что 0xc3 в латинском-1 указывает на Ã и 0xa9 на ©.

Ответ 2

Когда символы Unicode печатаются на стандартный вывод, используется sys.stdout.encoding. Предполагается, что символ не-Юникод находится в sys.stdout.encoding и просто отправляется на терминал. В моей системе (Python 2):

>>> import unicodedata as ud
>>> import sys
>>> sys.stdout.encoding
'cp437'
>>> ud.name(u'\xe9') # U+00E9 Unicode codepoint
'LATIN SMALL LETTER E WITH ACUTE'
>>> ud.name('\xe9'.decode('cp437')) 
'GREEK CAPITAL LETTER THETA'
>>> '\xe9'.decode('cp437') # byte E9 decoded using code page 437 is U+0398.
u'\u0398'
>>> ud.name(u'\u0398')
'GREEK CAPITAL LETTER THETA'
>>> print u'\xe9' # Unicode is encoded to CP437 correctly
é
>>> print '\xe9'  # Byte is just sent to terminal and assumed to be CP437.
Θ

sys.getdefaultencoding() используется только тогда, когда у Python нет другой опции.

Обратите внимание, что Python 3.6 или новее игнорирует кодировки в Windows и использует Unicode API для записи Unicode на терминал. Предупреждения UnicodeEncodeError и правильный символ отображаются, если шрифт поддерживает его. Даже если шрифт не поддерживает его, символы все равно могут быть вырезаны из n-вставки с терминала в приложение с поддерживающим шрифтом, и это будет правильно. Обновление!

Ответ 3

Python REPL пытается выбрать, какую кодировку использовать в вашей среде. Если он найдет что-то здравомыслящее, то все это Just Works. Это, когда он не может понять, что происходит, что он ошибается.

>>> print sys.stdout.encoding
UTF-8

Ответ 4

Вы указали кодировку, введя явную строку Unicode. Сравните результаты использования префикса u.

>>> import sys
>>> sys.getdefaultencoding()
'ascii'
>>> '\xe9'
'\xe9'
>>> u'\xe9'
u'\xe9'
>>> print u'\xe9'
é
>>> print '\xe9'

>>> 

В случае \xe9, тогда Python принимает вашу кодировку по умолчанию (Ascii), тем самым печатая... что-то пустое.

Ответ 5

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

import sys
stdin, stdout = sys.stdin, sys.stdout
reload(sys)
sys.stdin, sys.stdout = stdin, stdout
sys.setdefaultencoding('utf-8')