Python 2.7: Странное поведение Юникода
Я испытываю следующее поведение в Python 2.7:
>>> a1 = u'\U0001f04f' #1
>>> a2 = u'\ud83c\udc4f' #2
>>> a1 == a2 #3
False
>>> a1.encode('utf8') == a2.encode('utf8') #4
True
>>> a1.encode('utf8').decode('utf8') == a2.encode('utf8').decode('utf8') #5
True
>>> u'\ud83c\udc4f'.encode('utf8') #6
'\xf0\x9f\x81\x8f'
>>> u'\ud83c'.encode('utf8') #7
'\xed\xa0\xbc'
>>> u'\udc4f'.encode('utf8') #8
'\xed\xb1\x8f'
>>> '\xd8\x3c\xdc\x4f'.decode('utf_16_be') #9
u'\U0001f04f'
Чем объясняется такое поведение? Более конкретно:
- Я ожидаю, что две строки будут равны, если утверждение № 5 верно, а № 3 доказывает обратное.
- Кодирование обеих кодовых точек вместе, как в утверждении № 6, дает результаты, отличающиеся от того, когда кодируются по одному в № 7 и № 8. Похоже, две кодовые точки рассматриваются как одна 4-байтовая кодовая точка. Но что, если я действительно хочу, чтобы они рассматривались как две разные точки кода?
- Как вы можете видеть из № 9 числа в
a2
на самом делеa1
закодированы с использованием UTF-16-BE, но хотя они были определены как кодовые точки Unicode с использованием\u
внутри строки Unicode (!) Python все еще может каким-то образом достичь равенства в #5. Как это могло быть возможно?
Ничто не имеет смысла здесь! В чем дело?
1 ответ
Python 2 здесь нарушает стандарт Unicode, позволяя вам кодировать кодовые точки в диапазоне от U+D800 до U+DFFF, по крайней мере, в сборке UCS4. Из Википедии:
Стандарт Unicode постоянно резервирует эти значения кодовых точек для кодирования UTF-16 суррогатов высокого и низкого уровней, и им никогда не будет назначен символ, поэтому не должно быть оснований для их кодирования. Официальный стандарт Unicode гласит, что никакие формы UTF, включая UTF-16, не могут кодировать эти кодовые точки.
Официальный стандарт UTF-8 не имеет кодировки для кодовых точек суррогатной пары UTF-16, поэтому Python 3 вызывает исключение при попытке:
>>> '\ud83c\udc4f'.encode('utf8')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'utf-8' codec can't encode characters in position 0-1: surrogates not allowed
Но поддержка Unicode в Python 2 немного более элементарна, и поведение, которое вы наблюдаете, зависит от конкретного варианта сборки UCS2 / UCS4; в сборке UCS2 ваши переменные равны:
>>> import sys
>>> sys.maxunicode
65535
>>> a1 = u'\U0001f04f'
>>> a2 = u'\ud83c\udc4f'
>>> a1 == a2
True
потому что в такой сборке все не-BMP кодовые точки кодируются как суррогатные пары UTF-16 (расширение по стандарту UCS2).
Таким образом, в сборке UCS2 нет разницы между вашими двумя значениями, и выбор кодирования в полную кодовую точку, отличную от BMP, полностью допустим, если вы предполагаете, что захотите кодировать U+1F04F и другие подобные кодовые точки. Сборка UCS4 просто соответствует этому поведению.