Как получить надежный счетчик символов unicode в Python?

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

2 ответов


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

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

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

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

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

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


к сожалению, поведение интерпретатора CPython в версиях ранее 3.3 зависит от того, построен ли он с "узкой" или "широкой" поддержкой юникода. Так что тот же код, например вызов 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