Почему Python печатает символы Юникода, если кодировкой по умолчанию является ASCII? - PullRequest
135 голосов
/ 08 апреля 2010

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

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

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

EDIT

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

Ответы [ 6 ]

100 голосов
/ 23 февраля 2014

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

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

Бинго!

Если вы сейчас попытаетесь вывести какой-либо юникод-символ за пределы 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 Terminal) и настроим терминал для декодирования вывода с помощью ISO-8859-1 или латинского-1 (графические терминалы обычно имеют опцию Установите кодировку символов в одном из выпадающих меню).Обратите внимание, что это не меняет фактическую кодировку среды оболочки , а только изменяет способ, которым сам терминал 1029 * будет декодировать выданный им результат, немного как веб-браузер.Поэтому вы можете изменить кодировку терминала независимо от среды оболочки.Затем давайте запустим 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, используя latin-1, но latin-1 идет от 0 до 255 и, таким образом, декодирует только потоки 1 байт за раз.0xc3a9 имеет длину 2 байта, поэтому латинский декодер-1 интерпретирует его как 0xc3 (195) и 0xa9 (169), что дает 2 символа: Ã и ©.

(3) python кодирует кодовую точку unicode u '\xe9 '(233) со схемой latin-1.Оказывается, латинский-1 диапазон кодовых точек составляет 0-255 и указывает на тот же символ, что и Юникод в этом диапазоне.Следовательно, кодовые точки 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 (см. Дальнейшее объяснение) и поэтому не может преобразовать его в кодовую точку Unicode.Кодовая точка не найдена, символ не напечатан.

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

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

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


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

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

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

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

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

UTF-8 кодирование кодовых точек Unicode в диапазоне 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 кодирование кодовых точек 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.

например. 'é' кодовая точка 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. Что именно так, как терминал получает его. Если ваш терминал настроен на декодирование строк с использованием latin-1 (одна из устаревших кодировок, не относящихся к юникоду), вы увидите Ã ©, потому что просто так получается, что 0xc3 в latin-1 указывает на Ã и 0xa9 на ©.

25 голосов
/ 08 апреля 2010

Когда символы Юникода печатаются на стандартный вывод, используется sys.stdout.encoding. Предполагается, что не-Unicode символ находится в 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 и правильный символ отображается, если шрифт поддерживает это. Даже если шрифт не поддерживает его, символы могут быть вставлены из терминала в приложение с поддерживающим шрифтом, и это будет правильно. Обновление!

8 голосов
/ 08 апреля 2010

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

>>> print sys.stdout.encoding
UTF-8
4 голосов
/ 08 апреля 2010

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

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

>>> 

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

0 голосов
/ 01 мая 2018

Согласно Python по умолчанию / неявные строковые кодировки и преобразования :

  • Когда print ing unicode, это encode d с <file>.encoding.
    • когда encoding не установлен, unicode неявно преобразуется в str (поскольку кодек для этого sys.getdefaultencoding(), то есть ascii, любые национальные символы вызовут UnicodeEncodeError)
    • для стандартных потоков, encoding выводится из среды. Обычно он устанавливается для потоков tty (из настроек локали терминала), но, скорее всего, не будет установлен для каналов
      • так что print u'\xe9', вероятно, будет успешным, когда выход поступит на терминал, и потерпит неудачу, если он перенаправлен. Решением является encode() строка с желаемой кодировкой перед print ing.
  • Когда print ing str, байты отправляются в поток как есть. Какие символы показывает терминал, будет зависеть от его языковых настроек.
0 голосов
/ 12 августа 2014

у меня работает:

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