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

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

Итак, вот моя ситуация: скажем, у меня есть обычная строка Unicode 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 - похоже, никаких различий нет.


person MattDMo    schedule 01.07.2016    source источник
comment
tweepy (и вообще Твиттер, я полагаю), похоже, этим занимается. Упоминание об этом здесь в надежде, что больше поисков Google по этой проблеме найдут этот ответ.   -  person tripleee    schedule 10.11.2019


Ответы (2)


Вы смешали буквальную строку \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 содержит буквальные символы \ ud83d \ ude4f (12 символов); вы не должны получать суррогатную пару:

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

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

person jfs    schedule 01.07.2016

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

Суррогаты - это способ выразить кодовые точки Unicode, превышающие U + FFFF.

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

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

(Строго говоря, область суррогатов делится на две половины; первая суррогатная мать в паре должна происходить от половины суррогатов высокого уровня, а вторая - от половины суррогатов низкого уровня. Как ни странно, суррогаты высокого уровня U + D800-U + DBFF имеют номера кодовых точек ниже, чем у младших суррогатов U + DC00-U + DFFF.)

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

Другими словами, хотя U + 12345 можно выразить с помощью суррогатная пара U + D808 U + DF45, вы должны просто выразить ее напрямую, если вы специально не используете UTF-16.

Более подробно, вот как это будет выражено в 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 < / а>

person tripleee    schedule 06.02.2019