Почему Python печатает символы юникода, когда кодировка по умолчанию-ASCII?
из оболочки Python 2.6:
>>> import sys
>>> print sys.getdefaultencoding()
ascii
>>> print u'xe9'
é
>>>
Я ожидал, что после инструкции print будет какая-то тарабарщина или ошибка, так как символ "é" не является частью ASCII, и я не указал кодировку. Я думаю, я не понимаю, что ASCII является кодировкой по умолчанию означает.
EDIT
я переместил редактирование в ответы раздел и принял его как предложено.
6 ответов:
благодаря кусочкам из разных ответов, я думаю, что мы можем сшить объяснение.
при попытке напечатать строку unicode, u '\xe9', Python неявно пытается кодировать эту строку, используя схему кодирования, хранящуюся в настоящее время в sys.стандартный вывод.кодирование. 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), и мы установим терминал для декодирования вывода с помощью ISO-8859-1 aka latin-1 (графические терминалы обычно имеют возможность Установить Кодировку Символов в одном из своих выпадающее меню.) Обратите внимание, что это не меняет фактический оболочки среды кодировка, это только меняет способ терминал сам будет декодировать вывод, который он дал, немного похож на веб-браузер. Поэтому вы можете изменить кодировку терминала, независимо от среды оболочки. Давайте тогда запустим Python из оболочки и проверим, что sys.стандартный вывод.кодировка устанавливается в кодировку среды оболочки (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 выводит двоичную строку как есть, терминал получает ее и пытается сопоставить ее значение с картой символов latin-1. В латинском-1, 0xe9 или 233 дает символ "é" и так вот что отображает терминал.
(2) python пытается имплицитно кодируйте строку Unicode с любой схемой, установленной в настоящее время в sys.стандартный вывод.кодировка, в данном случае это "UTF-8". После кодирования UTF-8 результирующая двоичная строка является '\xc3\xa9 ' (см. более позднее объяснение). Терминал получает поток как таковой и пытается декодировать 0xc3a9 с помощью latin-1, но latin-1 идет от 0 до 255 и так, только декодирует потоки по 1 байту за раз. 0xc3a9 имеет длину 2 байта, поэтому декодер latin-1 интерпретирует его как 0xc3 (195) и 0xa9 (169), и это дает 2 символа: Ã и ©.
(3) python кодирует кодовую точку unicode u'\xe9' (233) по схеме latin-1. Оказывается, диапазон кодовых точек latin-1 равен 0-255 и указывает на тот же символ, что и Unicode в этом диапазоне. Таким Образом, Unicode кодовые точки в этом диапазоне будут давать одно и то же значение при кодировании на латинице-1. Так что U'xe9\' (233), закодированных в латинские-1 также дает двоичную строку '\xe9'. Терминал получает это значение и пытается сопоставить его на карте символов latin-1. Как и в случае (1), он дает "é", и это то, что отображается.
теперь давайте изменим настройки кодировки терминала на UTF-8 из выпадающего меню (как вы изменили бы настройки кодировки вашего веб-браузера). Нет необходимости, чтобы остановить Python или перезагрузить оболочка. Кодировка терминала теперь матчи в Python. Давайте попробуем еще раз печатать:
>>> print '\xe9' # (4) >>> print u'\xe9' # (5) é >>> print u'\xe9'.encode('latin-1') # (6) >>>(4) python выводит a binary строку как есть. Терминал пытается декодировать этот поток с помощью UTF-8. Но UTF-8 не понимает значение 0xe9 (см. более позднее объяснение) и поэтому не может преобразовать его в кодовую точку unicode. Кодовая точка не найдена, символ не напечатан.
(5) python пытается имплицитно кодировать строку в Юникоде с тем, что в sys.стандартный вывод.кодирование. Еще "УТФ-8". Результирующая двоичная строка - '\xc3\xa9'. Терминал получает поток и пытается декодировать 0xc3a9 также с помощью UTF-8. Он возвращает значение кода 0xe9 (233), которое на карте символов Юникода указывает на символ "é". Терминал отображает "é".
(6) python кодирует строку unicode с латиницей-1, она дает двоичную строку с тем же значением '\xe9'. Опять же, для терминала это почти то же самое, что и case (4).
выводы: - Python выводит строки, отличные от unicode, как необработанные данные, не учитывая его кодировку по умолчанию. Терминал просто отображает их, если его текущая кодировка соответствует данным. - Python выводит строки Unicode после кодирования их с помощью схемы, указанной в sys.стандартный вывод.кодирование. - Python получает этот параметр из среды оболочки. - терминал отображает вывод в соответствии с собственными настройками кодирования. - кодирование терминала не зависит от Шелла.
более подробная информация о unicode, UTF-8 и latin-1:
Unicode-это в основном таблица символов, в которой некоторые ключи (кодовые точки) были условно назначены для указания на некоторые символы. например, по соглашению было решено, что ключ 0xe9 (233) является значением, указывающим на символ 'é'. ASCII и Unicode используют те же кодовые точки от 0 до 127, что и латинский-1 и Unicode от 0 до 255. То есть, 0x41 указывает на 'A' в ASCII, латинский-1 и Юникод, 0xc8 указывает на 'Ü' в кодировке Latin-1 и Unicode, 0xe9 указывает на 'é' в Латинской-1 и Unicode.
при работе с электронными устройствами кодовые точки Unicode нуждаются в эффективном способе представления в электронном виде. Вот что такое схемы кодирования. Существуют различные схемы кодирования Юникода (utf7, UTF-8, UTF-16, UTF-32). Наиболее интуитивным и прямым подходом к кодированию было бы просто использовать значение кодовой точки На карте Unicode в качестве ее значения для ее электронной формы, но Unicode в настоящее время имеет более миллиона кодовых точек, что означает, что некоторые из них требуют 3 байта для выражения. Чтобы эффективно работать с текстом, сопоставление 1 к 1 было бы довольно непрактичным, поскольку это потребовало бы, чтобы все кодовые точки хранились в точно таком же объеме пространства, как минимум 3 байта на символ, независимо от их фактической потребности.
большинство схем кодирования имеют недостатки в отношении требования к пространству, наиболее экономические из них не охватывают весь код unicode точки, например ascii охватывает только первые 128, в то время как латинский-1 охватывает первые 256. Другие, которые пытаются быть более всеобъемлющими, также оказываются расточительными, поскольку они требуют больше байтов, чем необходимо, даже для обычных "дешевых" символов. Например, UTF-16 использует минимум 2 байта на символ, в том числе в диапазоне ascii ("B", который равен 65, по-прежнему требует 2 байта памяти в UTF-16). UTF-32 еще более расточителен, поскольку он хранит все символы в 4 байтах.
UTF-8 оказывается, умно разрешили дилемму, со схемой, способной хранить кодовые точки с переменным количеством байтовых пространств. В рамках своей стратегии кодирования UTF-8 laces кодирует точки с битами флага, которые указывают (предположительно для декодеров) их требования к пространству и их границы.
кодировка 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кодовые точки Юникода 0xe9 после кодировки UTF-8 становятся 0xc3a9. Именно так терминал получает оно. Если ваш терминал настроен на декодирование строк с использованием latin-1 (одна из устаревших кодировок, отличных от unicode), вы увидите é, потому что так получилось, что 0xc3 на латинском языке-1 указывает на à и 0xa9 на ©.
когда символы Юникода печатаются в stdout, есть. Предполагается, что символ не в Юникоде находится в
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 и использует API Unicode для записи Unicode на терминал. Нет unicodeencodeerror предупреждения и правильный символ отображается, если шрифт поддерживать ее. Даже если шрифт не support it символы все еще могут быть вырезаны-N-вставлены из терминала в приложение с поддерживающим шрифтом, и это будет правильно. Обновление!
Python REPL пытается выбрать, какую кодировку использовать из вашей среды. Если он находит что-то разумное, то все это просто работает. Это когда он не может понять, что происходит, что он выходит из строя.
>>> print sys.stdout.encoding UTF-8
вы есть укажите кодировку, введя явную строку Юникода. Сравните результаты не использования
uпрефикс.>>> import sys >>> sys.getdefaultencoding() 'ascii' >>> '\xe9' '\xe9' >>> u'\xe9' u'\xe9' >>> print u'\xe9' é >>> print '\xe9' >>>В случае
\xe9затем Python принимает кодировку по умолчанию (Ascii), таким образом, печать ... что-то пустое.
по состоянию на Python по умолчанию / неявные строковые кодировки и преобразования:
- , когда
unicode, этоencodeС<file>.encoding.
- когда
encodingне установлено,unicodeнеявно преобразован вstr(так как кодек для этогоsys.getdefaultencoding(), т. е.ascii, любые национальные символы вызовутUnicodeEncodeError)- для стандартных потоков,
encodingвыводится из окружающей среды. Это обычно устанавливается fotttyпотоки (из настроек локали терминала), но, скорее всего, не будут установлены для труб
- так
print u'\xe9'скорее всего, будет успешным, когда выход на терминал, и не удастся, если он перенаправлен. Решение заключается вencode()строка с нужной кодировкой перед- , когда
str, байты отправляются в поток как есть. Какие глифы показывает терминал, будет зависеть от его локали настройки.
это работает для меня:
import sys stdin, stdout = sys.stdin, sys.stdout reload(sys) sys.stdin, sys.stdout = stdin, stdout sys.setdefaultencoding('utf-8')
Comments