Запутался в представлениях юникода

Меня смущает шестнадцатеричное представление Unicode. У меня есть пример файла с одним символом математического интеграла. Это U + 222B. Если я копирую файл или редактирую его в vi, я получаю отображаемый знак интеграла. Шестнадцатеричный дамп файла показывает, что его шестнадцатеричное содержимое равно 88e2 0aab.

В python я могу создать интегральный символ юникода и напечатать p-рендеринг на моем терминале и знаке интеграла.

>>> p=u'\u222b'
>>> p
u'\u222b'
>>> print p
∫

Что меня смущает, так это то, что я могу открыть файл с интегральным знаком, получить интегральный символ, но шестнадцатеричное содержимое другое.

>>> c=open('mycharfile','r').read()
>>> c
'\xe2\x88\xab\n'
>>> print c
∫

Один из них является объектом Unicode, а другой — простой строкой, но какова связь между двумя шестнадцатеричными кодами, по-видимому, для одного и того же символа? Как бы мне вручную конвертировать один в другой?


person Keir    schedule 10.09.2013    source источник
comment
0x222b = 8747 — это целое число кода, которое в Юникоде связано со знаком интеграла . когда вы записываете текст в файл или отправляете его по сети, он всегда должен быть сериализован в биты — обычно предпочтительными единицами измерения здесь являются октеты (байты). ряды 0xe2, 0x88, 0xab (или 0b11100010, 0b10001000, 0b10101011 в двоичном формате) представляют собой кодировку UTF-8 (en.wikipedia.org/wiki/UTF-8) из 0x222b. между прочим, три начальных 1 в первом байте говорят вам, что эта кодовая точка закодирована в трех байтах: UTF-8 имеет как переменную ширину, так и «синхронизацию».   -  person flow    schedule 11.09.2013
comment
Обязательно: bit.ly/unipain   -  person Daenyth    schedule 11.09.2013
comment
эта битая ссылка выглядит многообещающе. также следует отметить, что обработка Unicode в Py3 намного разумнее, чем в Py2 — до такой степени, что этот единственный фактор должен иметь большое значение при принятии решения о том, какую версию Python использовать. к сожалению, между Py2 и Py3 существует нехороший и продолжающийся разрыв с отставанием поддержки сторонних библиотек. где сияет Py3, так это то, что старые «строки ASCII» исчезли; вы всегда имеете дело с буфером байтов (закодированным) или текстом (Unicode) (декодированным). это просто изменило концепции / имена вещей, но тогда программирование во многом связано с концепциями и именами.   -  person flow    schedule 11.09.2013
comment
Помимо измененных концепций и имен, Py3 также имеет более безопасное поведение, заключающееся в отсутствии неявного преобразования между байтами и строками. Попробуйте объединить их, и он немедленно пожалуется, что намного лучше, чем подход Py2, когда он обычно работает, но терпит неудачу, когда кодировка по умолчанию не может преобразовать.   -  person Peter DeGlopper    schedule 12.09.2013
comment
Я все еще что-то упускаю. Пары байтов перевернуты из 88e2 0aab, содержащихся в шестнадцатеричном редактировании символа, и один символ является возвратом, поэтому у нас остаются 0xe2, 0x88, 0xab.   -  person Keir    schedule 12.09.2013
comment
Это означает, что первый байт равен двум, нужна еще пара двоек. Следующие два бита 10 означают, что это односимвольный символ, но следующие шесть битов - это восьмерка, а не двойка?   -  person Keir    schedule 12.09.2013


Ответы (2)


Обычная строка была закодирована с использованием UTF-8, одного из множества способов представления кодовых точек Unicode в байтах. UTF-8 — это многобайтовая кодировка, которая имеет часто полезную функцию, заключающуюся в том, что она является надмножеством ASCII — один и тот же байт кодирует любой символ ASCII в UTF-8 или в ASCII.

В Python 2.x используйте метод encode для объекта Unicode для его кодирования и decode или конструктор unicode для его декодирования:

>>> u'\u222b'.encode('utf8')
'\xe2\x88\xab'
>>> '\xe2\x88\xab'.decode('utf8')
u'\u222b'
>>> unicode('\xe2\x88\xab', 'utf8')
u'\u222b'

print при задании аргумента Unicode неявно кодирует его. В моей системе:

>>> sys.stdout.encoding
'UTF-8'

См. этот ответ для более подробного обсуждения поведения print: Почему Python печатает символы Юникода, если по умолчанию используется кодировка ASCII?

Python 3 обрабатывает вещи немного по-другому; изменения задокументированы здесь: http://docs.python.org/3.0/whatsnew/3.0.html#text-vs-data-instead-of-unicode-vs-8-bit

person Peter DeGlopper    schedule 10.09.2013

Хорошо, у меня есть. Спасибо за ответы. я хотел посмотреть, как сделать преобразование, а не преобразовывать строку с помощью Python.

преобразование работает таким образом.

Если у вас есть символ Юникода, в моем примере это интегральный символ.

Octal дамп производит

echo -n "∫"|od -x
0000000 88e2 00ab

Каждая шестнадцатеричная пара перевернута, так что это действительно означает

e288ab00

Первый шестнадцатеричный символ — E. Старший бит означает, что это строка Unicode, а следующие два бита указывают, что для представления символа требуется 3 три байта (16 бит). Первые два бита оставшихся шестнадцатеричных цифр отбрасываются (они означают, что они являются юникодными). Полный битовый поток

111000101000100010101011

Отбросьте первые 4 бита и первые два бита оставшихся шестнадцатеричных цифр.

0010001000101011

Повторное выражение этого в шестнадцатеричном формате

222B

Они у вас есть!

person Keir    schedule 12.09.2013
comment
старший бит означает, что это не совсем правильная строка Unicode. Это стирает грань между использованием символов, которых не было в ASCII, и деталями кодировки, характерными для UTF-8. Точнее, старший бит означает, что это часть многобайтовой кодировки; количество начальных единиц перед первым 0 указывает общее количество байтов в кодировке (в данном случае 3). У вас правильная фактическая обработка, но я рекомендую внимательно прочитать эссе Джоэла о программном обеспечении, на которое ссылается Jongware. Юникод и кодировки — связанные понятия, но не настолько взаимозаменяемые, как предполагает эта формулировка. - person Peter DeGlopper; 12.09.2013