Помогите мне понять, почему 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.