Как получить надежное количество символов Юникода в Python? - PullRequest
8 голосов
/ 03 августа 2011

Google App Engine использует Python 2.5.2, по-видимому, с включенной UCS4.Но хранилище данных GAE использует UTF-8 внутри.Поэтому, если вы сохраняете u '\ ud834 \ udd0c' (длина 2) в хранилище данных, при извлечении его вы получаете '\ U0001d10c' (длина 1).Я пытаюсь посчитать количество символов Юникода в строке таким образом, чтобы получить одинаковый результат до и после его сохранения.Поэтому я пытаюсь нормализовать строку (от u '\ ud834 \ udd0c' до '\ U0001d10c'), как только получу ее, прежде чем вычислять ее длину и помещать в хранилище данных.Я знаю, что могу просто закодировать его в UTF-8, а затем снова декодировать, но есть ли более простой / эффективный способ?

Ответы [ 2 ]

4 голосов
/ 03 августа 2011

Я знаю, что могу просто закодировать его в UTF-8, а затем снова декодировать

Да, это обычная идиома для решения проблемы, когда у вас есть «суррогаты UTF-16 вUCS-4 строка ».Но, как сказала Механическая улитка, этот ввод является неправильным, и вы должны исправить все, что произвело его в предпочтениях.

есть ли более простой / эффективный способ?

Ну ... вы могли бы сделать это вручную с помощью регулярного выражения, например:

re.sub(
    u'([\uD800-\uDBFF])([\uDC00-\uDFFF])',
    lambda m: unichr((ord(m.group(1))-0xD800<<10)+ord(m.group(2))-0xDC00+0x10000),
    s
)

Конечно, не более простым ... У меня также есть сомнения относительно того,на самом деле эффективнее!

2 голосов
/ 30 марта 2012

К сожалению, поведение интерпретатора CPython в версиях, предшествующих 3.3, зависит от того, построен ли он с «узкой» или «широкой» поддержкой Unicode. Таким образом, один и тот же код, такой как вызов len, может иметь разные результаты в разных сборках стандартного интерпретатора. См. этот вопрос для примеров.

Различие между «узким» и «широким» заключается в том, что «узкие» интерпретаторы внутренне хранят 16-битные кодовые единицы (UCS-2), тогда как «широкие» интерпретаторы внутренне хранят 32-битные кодовые единицы (UCS-4). Код точек U + 10000 и выше (вне базовой многоязычной плоскости) имеют len из двух на «узких» интерпретаторах, потому что для представления двух кодов UCS-2 единиц их (используя суррогаты), и это то, что len измеряет. В "широких" сборках только один код UCS-4 требуется единица для не-BMP кода точка , поэтому для этих сборок len - один для таких кодовых точек.

Я подтвердил, что ниже обрабатывает все строки unicode, независимо от того, содержат они суррогатные пары или нет, и работает в CPython 2.7 как с узкими, так и с широкими сборками. (Возможно, указание строки типа u'\ud83d\udc4d' в широком интерпретаторе отражает утвердительное желание представить полный суррогатный код точка в отличие от кода с частичным символом единица и, следовательно, не является автоматически ошибка, которая будет исправлена, но я игнорирую это здесь. Это крайний случай и обычно не желательный вариант использования.)

Используемый ниже трюк @invoke позволяет избежать повторных вычислений, не добавляя ничего к __dict__.

модуля.
invoke = lambda f: f()  # trick taken from AJAX frameworks

@invoke
def codepoint_count():
  testlength = len(u'\U00010000')  # pre-compute once
  assert (testlength == 1) or (testlength == 2)
  if testlength == 1:
    def closure(data):  # count function for "wide" interpreter
      u'returns the number of Unicode code points in a unicode string'
      return len(data.encode('UTF-16BE').decode('UTF-16BE'))
  else:
    def is_surrogate(c):
      ordc = ord(c)
      return (ordc >= 55296) and (ordc < 56320)
    def closure(data):  # count function for "narrow" interpreter
      u'returns the number of Unicode code points in a unicode string'
      return len(data) - len(filter(is_surrogate, data))
  return closure

assert codepoint_count(u'hello \U0001f44d') == 7
assert codepoint_count(u'hello \ud83d\udc4d') == 7
...