Как работать с суррогатными парами в 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