2010-04-08 3 views
126

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

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

я ожидал иметь либо какой-то бред или ошибку после оператора печати, так как «é» символ не является частью ASCII, и я не указал кодировку , Наверное, я не понимаю, что ASCII является средством кодирования по умолчанию.

EDIT

I moved the edit to the Answers section and accepted it as suggested.

+5

Было бы очень хорошо, если бы вы могли превратить это * редактировать * в ответ вместо и принимаю его. – mercator

+2

Печать '' \ xe9'' в терминале, настроенном для UTF-8, будет ** не ** печатать 'é'. Он напечатает заменяющий символ (обычно знак вопроса), поскольку '\ xe9' не является допустимой последовательностью UTF-8 (ему не хватает двух байтов, которые должны были следовать за этим ведущим байтом). Это будет, конечно, ** не ** интерпретироваться как латинский-1. –

+2

@MartijnPieters Я подозреваю, что вы, возможно, просмотрели часть, где я указал, что терминал настроен на декодирование в ISO-8859-1 (latin1), когда я выводил '\ xe9' для печати' é'. –

ответ

84

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

При попытке распечатать строку юникода, 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. 

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

$ python 

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

Bingo!

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

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

Позволяет выйти из Python и отбросить Баш оболочку.

Теперь мы будем наблюдать, что происходит после выходов строк Python. Для этого мы сначала начнем оболочку bash в графическом терминале (я использую терминал Gnome), и мы установим, что терминал будет декодировать выход с ISO-8859-1 aka latin-1 (графические терминалы обычно имеют опцию Set Кодировка символов в одном из раскрывающихся меню). Обратите внимание, что это не меняет фактическую кодировку оболочки , она меняет способ только того, что терминал сам будет декодировать выводимый результат, немного похожий на веб-браузер. Поэтому вы можете изменить кодировку терминала, независимо от среды оболочки. Давайте затем запустить 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) попытки питона неявно кодируют строки 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 кодирует кодовую точку Unicode u '\ xe9' (233) с помощью схемы latin-1. Выключает диапазон кодовых точек латинского-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) питон выводит двоичных строку как есть. Терминал пытается декодировать этот поток с помощью 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 выводит строки не-unicode как необработанные данные, не считая по умолчанию кодировки. Терминал просто появляется, чтобы отображать их, если его текущее кодирование соответствует данным. - Python выводит строки Unicode после их кодирования с использованием схемы, указанной в sys.stdout.encoding. - Python получает эту настройку из среды оболочки. - терминал отображает вывод в соответствии с его собственными настройками кодирования. - кодировка терминала не зависит от оболочки.


Более подробную информацию о Unicode, 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 (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) 
  • рентгеновские покажет фактическое пространство, отведенное на "магазин" точка-код при кодировании
  • Ведущий 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 кодирование кодовых точек Unicode выше 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 

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

+5

Отличное объяснение. Теперь я понимаю UTF-8! –

+2

Наконец-то я мог спать сегодня вечером ... – Alan

+0

Хорошо, я прочитал весь ваш пост примерно через 10 секунд. Он сказал: «Python отстой, когда дело доходит до кодирования». – Andrew

8

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

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

просто из любопытства, как бы сменить sys.stdout.encoding на ascii? –

+1

Вы бы этого не сделали. Вы должны использовать 'codecs.EncodedFile()', чтобы обернуть его. –

+2

@TankorSmash Я получаю 'TypeError: readonly attribute' на 2.7.2 – Kos

4

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

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

>>> 

В случае \xe9 затем Python предполагает кодировку по умолчанию (ASCII), таким образом, печать ... что-то пустое.

+1

, поэтому, если я хорошо понимаю, когда я печатаю строки unicode (кодовые точки), python предполагает, что я хочу, чтобы результат был закодирован в utf-8, вместо этого просто пытаться дать мне то, что он * мог бы быть в ascii? –

+1

@mike: AFAIK, что вы сказали, правильно. Если бы он * распечатывал символы Юникода, но кодировался как ASCII, все бы выглядело искаженным и, вероятно, все новички спрашивали: «Почему я не могу распечатать текст в Юникоде?» –

+2

Спасибо. Я на самом деле один из тех новичков, но прихожу от людей, у которых есть некоторое представление о юникоде, и именно поэтому это поведение отбрасывает меня. –

24

Когда символы Unicode печатаются на стандартный вывод, используется sys.stdout.encoding. Предполагается, что символ не Unicode находится в sys.stdout.encoding и просто отправляется на терминал. На моей системе:

>>> import unicodedata as ud 
>>> import sys 
>>> sys.stdout.encoding 
'cp437' 
>>> ud.name(u'\xe9') 
'LATIN SMALL LETTER E WITH ACUTE' 
>>> ud.name('\xe9'.decode('cp437')) 
'GREEK CAPITAL LETTER THETA' 
>>> import unicodedata as ud 
>>> ud.name(u'\xe9') 
'LATIN SMALL LETTER E WITH ACUTE' 
>>> '\xe9'.decode('cp437') 
u'\u0398' 
>>> ud.name(u'\u0398') 
'GREEK CAPITAL LETTER THETA' 
>>> print u'\xe9' 
é 
>>> print '\xe9' 
Θ 

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

-1

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

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

Дешевый грязный хак, который неизбежно сломает что-то еще. Это не сложно сделать правильно! –

Смежные вопросы