Python: получение правильной длины строки, если она содержит суррогатные пары

Рассмотрим следующий обмен на IPython:

In [1]: s = u'華袞與緼同歸'

In [2]: len(s)
Out[2]: 8

Правильный вывод должен был быть 7, но поскольку пятый из этих семи китайских символов имеет высокую кодовую точку Unicode, он представлен в UTF-8 "суррогатной парой", а не просто одной простой кодовой точкой, и в результате Python считает, что это скорее два символа чем один.

Даже если я использую unicodedata, который возвращает суррогатную пару правильно, как одну кодовую точку (\U00026177), когда передается len() неправильная длина все еще возвращается:

In [3]: import unicodedata

In [4]: unicodedata.normalize('NFC', s)
Out[4]: u'\u83ef\u889e\u8207\u7dfc\U00026177\u540c\u6b78'


In [5]: len(unicodedata.normalize('NFC', s))
Out[5]: 8

Не предпринимая радикальных шагов, таких как перекомпиляция Python для UTF-32, есть ли простой способ получить правильную длину в подобных ситуациях?

Я нахожусь на IPython 0.13, Python 2.7.2, Mac OS 10.8.2.

3 ответа

Решение

Я думаю, что это было исправлено в 3.3. Увидеть:

http://docs.python.org/py3k/whatsnew/3.3.html
http://www.python.org/dev/peps/pep-0393/ (найдите wstr_length)

Я делаю функцию для этого на Python 2:

SURROGATE_PAIR = re.compile(u'[\ud800-\udbff][\udc00-\udfff]', re.UNICODE)
def unicodeLen(s):
  return len(SURROGATE_PAIR.sub('.', s))

Заменив суррогатные пары одним символом, мы "исправим" len функция. На обычных строках это должно быть довольно эффективно: поскольку шаблон не будет совпадать, исходная строка будет возвращена без изменений. Он также должен работать на широких (32-битных) сборках Python, так как кодирование суррогатных пар не будет использоваться.

Вы можете переопределить функцию len в Python (см.: Как работает len?) И добавить в нее оператор if для проверки очень длинного юникода.

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