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

Это продолжение конвертации в эмодзи. В этом вопросе у ОП была 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 - никаких различий, похоже, нет.

1 ответ

Решение

Вы смешали буквальную строку \ud83d в файле JSON на диске (шесть символов: \ u d 8 3 d) и один символ u'\ud83d' (указывается с использованием строкового литерала в исходном коде Python) в памяти. Это разница между len(r'\ud83d') == 6 а также len('\ud83d') == 1 на питоне 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').

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

Суррогаты - это способ выражения кодовых точек 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

Другие вопросы по тегам