Как работать с суррогатными парами в 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, там не было никаких различий.
Ответы
Ответ 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