Подтвердить что ты не робот

Как работать с суррогатными парами в Python?

Это продолжение перехода на Emoji. В этом вопросе OP имел json.dumps() -encoded с emoji, представленным как суррогатная пара - \ud83d\ude4f. У S/у него были проблемы с чтением файла и корректным переводом emoji, и правильный ответ заключался в json.loads() каждой строке из файла, а модуль json обрабатывал преобразование из суррогатной пары обратно (я предполагаю UTF8 -encoded) emoji.

Итак, вот моя ситуация: скажем, у меня есть только обычная строка юникода Python 3 с суррогатной парой в ней:

emoji = "This is \ud83d\ude4f, an emoji."

Как обработать эту строку, чтобы получить представление смайликов из него? Я хочу получить что-то вроде этого:

"This is 🙏, an emoji."
# or
"This is \U0001f64f, an emoji."

Я пробовал:

print(emoji)
print(emoji.encode("utf-8")) # also tried "ascii", "utf-16", and "utf-16-le"
json.loads(emoji) # and '.encode()' with various codecs

Как правило, я получаю ошибку, подобную UnicodeEncodeError: XXX codec can't encode character '\ud83d' in position 8: surrogates no allowed.

Я запускаю Python 3.5.1 в Linux, с $LANG установлен в en_US.UTF-8. Я запустил эти образцы как в интерпретаторе Python в командной строке, так и в IPython, запущенном в Sublime Text, там не было никаких различий.

4b9b3361

Ответ 1

Вы смешали личную строку \ud83d в json файле на диске (шесть символов: \ u d 8 3 d) и один символ u'\ud83d' (указанный с использованием строкового литерала в исходном коде Python) в памяти. Это разница между len(r'\ud83d') == 6 и len('\ud83d') == 1 на Python 3.

Если вы видите строку '\ud83d\ude4f' Python (2), тогда появляется ошибка вверх. Обычно вы не должны получать такую ​​строку. Если вы его получите, и вы не можете исправить восходящий поток, который его генерирует; вы можете исправить это с помощью обработчика ошибок surrogatepass:

>>> "\ud83d\ude4f".encode('utf-16', 'surrogatepass').decode('utf-16')
'🙏'

Python 2 был более разрешительным.

Примечание: даже если ваш json файл содержит символы literal\ud83d\ude4f (12); вы не должны получать суррогатную пару:

>>> print(ascii(json.loads(r'"\ud83d\ude4f"')))
'\U0001f64f'

Обратите внимание: результат 1 ('\U0001f64f'), а не суррогатная пара ('\ud83d\ude4f').

Ответ 2

Поскольку это повторяющийся вопрос, а сообщение об ошибке немного неясно, приведено более подробное объяснение.

Суррогаты - это способ выражения кодовых точек Unicode больше, чем U + FFFF.

Напомним, что изначально Unicode содержал 65 536 символов, но вскоре было обнаружено, что этого недостаточно для размещения всех символов мира.

В качестве механизма расширения для кодировки UTF-16 (в противном случае с фиксированной шириной) была создана зарезервированная область, содержащая механизм для выражения кодовых точек вне базовой многоязычной плоскости: любой за кодовой точкой в этой специальной области должен следовать другой символьный код из той же области, и вместе они будут выражать кодовую точку с номером, превышающим старый предел.

(Строго говоря, область суррогатов делится на две половины; первый суррогат в паре должен исходить от половины верхних суррогатов, а второй от нижних суррогатов.)

Это устаревший механизм, специально предназначенный для поддержки кодировки UTF-16, и его не следует использовать в других кодировках; им это не нужно, и в действующих стандартах конкретно говорится, что это запрещено.

Другими словами, в то время как U + 12345 можно выразить суррогатной парой U + D808 U + DF45, вы должны просто вместо этого выразить это напрямую.

Более подробно, вот как это будет выражаться в UTF-8 как один символ:

0xF0 0x92 0x8D 0x85

А вот соответствующая суррогатная последовательность:

0xED 0xA0 0x88
0xED 0xBD 0x85

Как уже предлагалось в принятом ответе, вы можете отправиться туда и обратно с чем-то вроде

>>> "\ud808\udf45".encode('utf-16', 'surrogatepass').decode('utf-16').encode('utf-8')
b'\xf0\x92\x8d\x85'

Возможно, см. также http://www.russellcottrell.com/greek/utilities/surrogatepaircalculator.htm