Оказывается, получить это право довольно сложно: у Python 2 и Python 3 есть некоторые тонкие проблемы с извлечением кодовых точек Unicode из строки.
Вплоть до Python 3.3 можно было компилировать Python в одном из двух режимов:
sys.maxunicode == 0x10FFFF
В этом режиме строки Unicode в Python поддерживают полный диапазон кодовых точек Unicode от U + 0000 до U + 10FFFF. Одна кодовая точка представлена одним строковым элементом:
>>> import sys
>>> hex(sys.maxunicode)
'0x10ffff'
>>> len(u'\U0001F40D')
1
>>> [c for c in u'\U0001F40D']
[u'\U0001f40d']
Это значение по умолчанию для Python 2.7 в Linux, а также универсально для Python 3.3 и более поздних версий во всех операционных системах.
sys.maxunicode == 0xFFFF
В этом режиме строки Unicode в Python поддерживают только диапазон кодовых точек Unicode от U + 0000 до U + FFFF. Любые кодовые точки от U + 10000 до U + 10FFFF представлены с использованием пары строковых элементов в кодировке UTF-16: *
>>> import sys
>>> hex(sys.maxunicode)
'0xffff'
>>> len(u'\U0001F40D')
2
>>> [c for c in u'\U0001F40D']
[u'\ud83d', u'\udc0d']
Это значение по умолчанию для Python 2.7 в macOS и Windows.
Это различие во время исполнения делает написание модулей Python для манипулирования строками Unicode как серии кодов очень неудобно.
Модуль кодовых точек
Чтобы решить эту проблему, я добавил новый модуль codepoints
в PyPI
:
https://pypi.python.org/pypi/codepoints/1.0
Этот модуль решает проблему, предоставляя API для преобразования строк Unicode в и из списков кодовых точек, независимо от базовой настройки для sys.maxunicode
::
>>> hex(sys.maxunicode)
'0xffff'
>>> snake = tuple(codepoints.from_unicode(u'\U0001F40D'))
>>> len(snake)
1
>>> snake[0]
128013
>> hex(snake[0])
'0x1f40d'
>>> codepoints.to_unicode(snake)
u'\U0001f40d'