Skip to content

Instantly share code, notes, and snippets.

@maizy
Last active August 29, 2015 14:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save maizy/5c7a8f369c5d78b72f9f to your computer and use it in GitHub Desktop.
Save maizy/5c7a8f369c5d78b72f9f to your computer and use it in GitHub Desktop.
python wide unicode

Поддержка unicode в python и проблема "wide" unicode

wide? wtf?

Unicode символы более U+FFFF (65535) называют "wide".

Вот полная таблица диапазонов unicode на данный момент:

name description range
Plane 0 U+0000…U+​FFFF
Plane 1 Supplementary multilingual plane (SMP) U+10000…U+​1FFFF
Plane 2 Supplementary ideographic plane (SIP) U+20000…U+​2FFFF
Planes 3-13 Unassigned U+30000…U+​DFFFF
Plane 14 Supplement­ary special-purpose plane (SSP) U+E0000…U+​EFFFF
Planes 15-16 Supplement­ary private use area (S PUA A/B) U+F0000…U+​10FFFF

В частности в SMP находятся emoji символы, например U+1f4a9 = 💩 (тут должен быть 💩, но вы его не увидите скорее всего)

Как в python 2.7?

Печально всё в python 2.7. По-умолчанию, внутри используется представление utf-16, которое не может работать с такими символами "по честному" и в unicode типе они становятся двумя "символами" ("суррогатные пары" в UTF-16).

Суррогатные пары лежат в диапазоне 0xD800—0xDFFF.

assert(len(u'\U0001f4a9') == 2)

Трюк с суррогатами сохранит такие символы работающими при таком преобразовании:

str в utf-8 -> unicode -> str в utf-8

Пример см. в test-python-narrow.output.

Можно пофиксить?

Можно, но сложно. Можно собрать python с особым ключом --enable-unicode=ucs4. Все ли C расширения правильно будут работать - вопрос.

Для C есть спец. макросы и в идеальном мире всё должно работать.

Подробнее тут - http://legacy.python.org/dev/peps/pep-0261/

Пример: вывода тестового скрипта со специальной сборкой python 2.7 - test-python-wide.output.

Сравните с narrow! Главное тут:

import sys
assert(sys.maxunicode > 65535)
assert(len(u'\U0001f4a9') == 1)

python 3

В 3-й версии всё ок, по-умолчанию она собирается с поддержкой "wide" unicode. Но просто так с 2 на 3 мы перейти не можем.

Пример работы тестового скрипта: test-python3.output.

И что теперь переходить на специальную версию python?

Не обязательно. Как я показывал выше суррогаты для того и придумали, чтобы это не ломалось. Пока мы работаем с "строками unicode" (type(my_string) is unicode) в python - например, взять, покрутить и выдать в xml/json/html через xsl и пр. – всё будет нормально. Если мы начнём строки обрабатывать, например, считать длину, итерироваться по символам и пр. будут проблемы.

Как было нужно делать?

http://utf8everywhere.org/

Те, кто думают, что utf-8 = utf-16 / 2 – не правы. Это разные способы кодирования. Utf-8 более универсален! Да, он имеет свои недостатки (не выровнен и пр), но он лучше + совместим c ascii (0-127) и не содержит хаков в виде суррогатных пар.

-- тест python --
текущая версия python: 2.7.8
внутренний размер python типа unicode: 65535 (если 65535 - narrow версия python, если больше - wide)
"обычный" unicode символ: \u266b = 9835
результат с кодировкой вашей консоли: ♫
unicode point (по версии python): 9835 (0x266b)
байтики при кодировании в utf-8: 11100010 10011001 10101011
len(♫) = 1
"широкий" unicode символ \U0001f4a9 = 128169:
результат с кодировкой вашей консоли: 💩
unicode point (по версии python): 55357 (0xd83d) 56489 (0xdca9)
байтики при кодировании в utf-8: 11110000 10011111 10010010 10101001
len(💩) = 2 (печалька, если тут 2)
-------------
-- тест python --
текущая версия python: 2.7.8
внутренний размер python типа unicode: 1114111 (если 65535 - narrow версия python, если больше - wide)
"обычный" unicode символ: \u266b = 9835
результат с кодировкой вашей консоли: ♫
unicode point (по версии python): 9835 (0x266b)
байтики при кодировании в utf-8: 11100010 10011001 10101011
len(♫) = 1
"широкий" unicode символ \U0001f4a9 = 128169:
результат с кодировкой вашей консоли: 💩
unicode point (по версии python): 128169 (0x1f4a9)
байтики при кодировании в utf-8: 11110000 10011111 10010010 10101001
len(💩) = 1 (печалька, если тут 2)
-------------
-- тест python --
текущая версия python: 3.4.2
внутренний размер python типа unicode: 1114111 (если 65535 - narrow версия python, если больше - wide)
"обычный" unicode символ: \u266b = 9835
результат с кодировкой вашей консоли: ♫
unicode point (по версии python): 9835 (0x266b)
байтики при кодировании в utf-8: 11100010 10011001 10101011
len(♫) = 1
"широкий" unicode символ \U0001f4a9 = 128169:
результат с кодировкой вашей консоли: 💩
unicode point (по версии python): 128169 (0x1f4a9)
байтики при кодировании в utf-8: 11110000 10011111 10010010 10101001
len(💩) = 1 (печалька, если тут 2)
-------------
#!/usr/bin/env python
# coding: utf-8
import sys
PY3 = sys.version_info.major >= 3
def print_res(text):
print(u'\tрезультат с кодировкой вашей консоли: {}'.format(text))
# не верный для смиволов больше U+FFFF в narrow версии python
print(u'\tunicode point (по версии python): {}'.format(' '.join('{0} (0x{0:04x})'.format(ord(i)) for i in text)))
text_utf8 = text.encode('utf-8') # в python3 тут bytes (итератор даёт int) в python2 str (итератор даёт str)
uniord = (lambda x: x) if PY3 else ord
print(u'\tбайтики при кодировании в utf-8: {}'.format(' '.join('{:08b}'.format(uniord(i)) for i in text_utf8)))
def print_test():
print(u' -- тест python --')
print(u'текущая версия python: {}'.format(sys.version[:5]))
print(u'внутренний размер python типа unicode: {} (если 65535 - narrow версия python, если больше - wide)'
.format(sys.maxunicode))
print('')
print(u'"обычный" unicode символ: \\u266b = 9835')
n = u'\u266b'
print_res(n)
print(u'len({}) = {}'.format(n, len(n)))
print('')
# в os x прямо распечатается картинка в консоле
print(u'"широкий" unicode символ \\U0001f4a9 = 128169:')
c = u'\U0001f4a9'
print_res(c)
print(u'len({}) = {} (печалька, если тут 2)'.format(c, len(c)))
print('')
print(u' -------------')
print('')
if __name__ == '__main__':
# allow piping trick in python 2.x
if not PY3:
import codecs
sys.stdout = codecs.getwriter('utf-8')(sys.stdout)
print_test()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment