Помогите мне понять, почему Unicode работает иногда с Python
Вот небольшая программа:
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
print('abcd kΩ ☠ °C √Hz µF ü ☃ ♥')
print(u'abcd kΩ ☠ °C √Hz µF ü ☃ ♥')
В Ubuntu, терминале Gnome, IPython делает то, что я ожидаю:
In [6]: run Unicodetest.py
abcd kΩ ☠ °C √Hz µF ü ☃ ♥
abcd kΩ ☠ °C √Hz µF ü ☃ ♥
Я получаю тот же вывод, если я вхожу в команды на trypython.org.
codepad.org, с другой стороны, выдает ошибку для второй команды:
abcd kΩ ☠ °C √Hz µF ü ☃ ♥
Traceback (most recent call last):
Line 6, in <module>
print(u'abcd kΩ ☠ °C √Hz µF ü ☃ ♥')
UnicodeEncodeError: 'ascii' codec can't encode character u'\u03a9' in position 6: ordinal not in range(128)
Contrariwise, IDLE в Windows управляет выходом первой команды, но не жалуется на второе:
>>>
abcd kΩ ☠°C √Hz µF ü ☃ ♥
abcd kΩ ☠ °C √Hz µF ü ☃ ♥
IPython в командной строке Windows или через версию Python (x, y) Console2 оба замалчивают первый вывод и жалуются на второе:
In [9]: run Unicodetest.py
abcd kΩ ☠ °C √Hz µF ü ☃ ♥
ERROR: An unexpected error occurred while tokenizing input
The following traceback may be corrupted or invalid
The error message is: ('EOF in multi-line statement', (15, 0))
---------------------------------------------------------------------------
UnicodeEncodeError Traceback (most recent call last)
Desktop\Unicodetest.py in <module>()
4 print('abcd kΩ ☠ °C √Hz µF ü ☃ ♥')
5
----> 6 print(u'abcd kΩ ☠ °C √Hz µF ü ☃ ♥')
7
8
C:\Python27\lib\encodings\cp437.pyc in encode(self, input, errors)
10
11 def encode(self,input,errors='strict'):
---> 12 return codecs.charmap_encode(input,errors,encoding_map)
13
14 def decode(self,input,errors='strict'):
UnicodeEncodeError: 'charmap' codec can't encode character u'\u2620' in position 8: character maps to <undefined>
WARNING: Failure executing file: <Unicodetest.py>
IPython внутри Python (x, y) Spyder делает то же самое, но по-другому:
In [8]: run Unicodetest.py
abcd kΩ ☠°C √Hz µF ü ☃ ♥
------------------------------------------------------------
Traceback (most recent call last):
File "Unicodetest.py", line 6, in <module>
print(u'abcd kΩ ☠°C √Hz µF ü ☃ ♥')
File "C:\Python26\lib\encodings\cp1252.py", line 12, in encode
return codecs.charmap_encode(input,errors,encoding_table)
UnicodeEncodeError: 'charmap' codec can't encode character u'\u03a9' in position 6: character maps to <undefined>
WARNING: Failure executing file: <Unicodetest.py>
(В sitecustomize.py Spyder устанавливает свой собственный SPYDER_ENCODING
на основе кодировки модуля локали, которая cp1252
для Windows 7.)
Что дает? Является ли одна из моих команд неправильной? Почему одна работает на некоторых платформах, а другая работает на других платформах? Как печатать символы Unicode последовательно без сбоев или привинчивания?
Есть ли альтернативный терминал для Windows, который ведет себя так же, как в Ubuntu? Кажется, что TCC-LE, Console2, Git Bash, PyCmd и т.д. - это всего лишь обертки для cmd.exe, а не замены. Есть ли способ запустить IPython внутри интерфейса, который использует IDLE?
Ответы
Ответ 1
I/O в Python (и большинстве других языков) основан на байтах. Когда вы пишете байтовую строку (str
в 2.x, bytes
в 3.x) в файл, байты просто записываются как-есть. Когда вы пишете строку Unicode (unicode
в 2.x, str
в 3.x) в файл, данные должны быть закодированы в последовательность байтов.
Для дальнейшего объяснения этого различия см. Погрузитесь в главу Python 3 по строкам.
print('abcd kΩ ☠ °C √Hz µF ü ☃ ♥')
Здесь строка является байтовой строкой. Поскольку кодировка вашего исходного файла - UTF-8, байты
'abcd k\xce\xa9 \xe2\x98\xa0 \xc2\xb0C \xe2\x88\x9aHz \xc2\xb5F \xc3\xbc \xe2\x98\x83 \xe2\x99\xa5'
Оператор print
записывает эти байты в консоль как есть. Но консоль Windows интерпретирует строки байтов как кодированные на кодовой странице "OEM", которая в США 437. Таким образом, строка, которую вы видите на экране,
abcd kΩ ☠ °C √Hz µF ü ☃ ♥
В вашей системе Ubuntu это не вызывает проблемы, потому что стандартная консольная кодировка - это UTF-8, поэтому у вас нет расхождения между кодировкой исходного кода и консольной кодировкой.
print(u'abcd kΩ ☠ °C √Hz µF ü ☃ ♥')
При печати строки Unicode строка должна быть закодирована в байты. Но он работает только в том случае, если у вас есть кодировка, поддерживающая эти символы. И вы этого не делаете.
- По умолчанию для кодировки IBM437 не хватает символов
☠☃♥
- windows-1252 в кодировке, используемой Spyder, не хватает символов
Ω☠√☃♥
.
Итак, в обоих случаях вы получаете UnicodeEncodeError, пытающийся напечатать строку.
Что дает?
Windows и Linux использовали совершенно разные подходы к поддержке Unicode.
Первоначально они работали почти так же: каждый языковой стандарт имеет собственную кодировку на основе языка char
( "кодовая страница ANSI" в Windows). Западные языки использовали ISO-8859-1 или windows-1252, русский использовали KOI8-R или windows-1251 и т.д.
Когда Windows NT добавила поддержку Unicode (в первые дни, когда предполагалось, что Unicode будет использовать 16-битные символы), он сделал это, создав параллельную версию своего API, которая использовала wchar_t
вместо char
, Например, функция MessageBox была разделена на две функции:
int MessageBoxA(HWND hWnd, const char* lpText, const char* lpCaption, unsigned int uType);
int MessageBoxW(HWND hWnd, const wchar_t* lpText, const wchar_t* lpCaption, unsigned int uType);
Функции "W" являются "реальными". Функции "A" существуют для обратной совместимости с Windows на основе DOS и в основном просто конвертируют свои строковые аргументы в UTF-16, а затем вызывают соответствующую функцию "W".
В мире Unix (в частности, Plan 9) запись совершенно новой версии POSIX API была непрактичной, поэтому поддержка Unicode была подобрана по-другому. Существующая поддержка многобайтового кодирования в локалях CJK была использована для реализации новой кодировки, известной теперь как UTF-8.
Преимущество UTF-8 в Unix-подобных системах и UTF-16 в Windows - огромная боль в заднице при написании кросс-платформенного кода, который поддерживает Unicode. Python пытается скрыть это от программиста, но печать на консоли является одной из "негерметичных абстракций" Джоэла.
Ответ 2
Возможны две причины:
- Кодирование Юникода
print
. Вы не можете выводить необработанный Unicode, поэтому print
нужно выяснить, как его преобразовать в поток байтов, ожидаемый консолью (он использует sys.stdout.encoding
AFAIK), что приводит нас к
- Поддержка консоли. Python не контролирует ваш терминал, поэтому, если он выплевывает UTF-8, а ваш терминал ожидает чего-то еще, вы получите искаженный вывод.
Ответ 3
Ваша проблема заключается в том, что ваша программа ожидает и выводит символы UTF-8, но консоли и различные бегуны python в Интернете используют другие кодовые страницы. Невозможно закодировать специальные символы, которые работают во всех кодировках без изменений. Однако, если вы решите использовать UTF-8 всюду, вы должны быть в безопасности.
Я думаю, что любой терминал в Windows будет делать - так что не беспокойтесь, чтобы отключить стандартный (cmd.exe) только из-за этого. Вместо этого измените кодировку терминала как UTF-8, чтобы соответствовать кодировке вашего python script.
К сожалению, я никогда не мог найти способ установить кодовую страницу в UTF-8 по умолчанию, поэтому это нужно делать каждый раз, когда вы открываете новую командную строку. Но это делается с помощью простой команды, поэтому она только наполовину плоха... Вы меняете кодировку на коммутационную кодовую страницу:
>chcp 65001
Current codepage is now 65001
Обратите внимание, что для этого вам нужно использовать один из стандартных шрифтов. Большинство источников в Интернете, похоже, предлагают Lucida Console.
Ответ 4
Выход Unicode из Python в консоль Windows просто не работает. Python не может быть убежден в том, чтобы испускать собственную кодировку Windows, которая ожидает широкие символы и UCS2.
Ответ 5
@dan04: Вы правы, что проблема в том, что кодировка файла не соответствует кодировке stdout. Тем не менее одним из способов решения проблемы является изменение кодировки файла. Таким образом, Windows Notepad ++ может использоваться для сохранения кода с кодировкой символов UTF-8.
Альтернативой является перекодировка GNU.