Специфичная для платформы семантика Unicode в Python 2.7

Ubuntu 11.10:

$ python
Python 2.7.2+ (default, Oct  4 2011, 20:03:08)
[GCC 4.6.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> x = u'\U0001f44d'
>>> len(x)
1
>>> ord(x[0])
128077

Windows 7:

Python 2.7.2 (default, Jun 12 2011, 15:08:59) [MSC v.1500 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> x = u'\U0001f44d'
>>> len(x)
2
>>> ord(x[0])
55357

Мой опыт Ubuntu связан с интерпретатором по умолчанию в дистрибутиве. Для Windows 7 я загрузил и установил рекомендованную версию, связанную с python.org. Я тоже не скомпилировал ни одного из них.

Характер разницы ясен для меня. (На Ubuntu строка представляет собой последовательность кодовых точек, а в Windows 7 - последовательность блоков кода UTF-16.) Мои вопросы:

  • Почему я наблюдаю эту разницу в поведении? Это связано с тем, как построен интерпретатор, или с разницей в зависимых системных библиотеках?
  • Есть ли способ настроить поведение интерпретатора Windows 7, чтобы согласиться с Ubuntu, что я могу сделать в Eclipse PyDev (моя цель)?
  • Если мне нужно перестроить, есть ли готовые интерпретаторы Windows 7, которые ведут себя как Ubuntu выше из надежного источника?
  • Есть ли какие-либо обходные пути для этой проблемы, кроме ручного подсчета суррогатов в строках unicode только для Windows (blech)?
  • Это оправдывает отчет об ошибке? Есть ли вероятность, что такой отчет об ошибке будет рассмотрен в 2.7?

Ответ 1

В Ubuntu у вас есть "wide" Python build, где строки UTF-32/UCS-4. К сожалению, пока это не доступно для Windows.

Строки Windows будут узкими на время, основанные на том, что там было несколько запросов для широких символов, эти запросы в основном от жестких программистов, имеющих возможность покупать собственный Python и сама Windows сильно смещена к 16-разрядным символам.

Python 3.3 будет гибкое строковое представление, в котором вам не нужно заботиться о том, используют ли строки Unicode 16-битные или 32-битные кода.

До тех пор вы можете получить коды из строки UTF-16 с помощью

def code_points(text):
    utf32 = text.encode('UTF-32LE')
    return struct.unpack('<{}I'.format(len(utf32) // 4), utf32)

Ответ 2

отличный вопрос! Я недавно упал в эту кроличью яму.

Ответ на

@dan04 вдохновил меня на то, чтобы развернуть его в подкласс unicode, который обеспечивает последовательную индексацию, нарезку и len() на узких и широких строках Python 2:

class WideUnicode(unicode):
  """String class with consistent indexing, slicing, len() on both narrow and wide Python."""
  def __init__(self, *args, **kwargs):
    super(WideUnicode, self).__init__(*args, **kwargs)
    # use UTF-32LE to avoid a byte order marker at the beginning of the string
    self.__utf32le = unicode(self).encode('utf-32le')

  def __len__(self):
    return len(self.__utf32le) / 4

  def __getitem__(self, key):
    length = len(self)

    if isinstance(key, int):
      if key >= length:
        raise IndexError()
      key = slice(key, key + 1)

    if key.stop is None:
      key.stop = length

    assert key.step is None

    return WideUnicode(self.__utf32le[key.start * 4:key.stop * 4]
                       .decode('utf-32le'))

  def __getslice__(self, i, j):
    return self.__getitem__(slice(i, j))

open sourced here, public domain. пример использования:

text = WideUnicode(obj.text)
for tag in obj.tags:
  text = WideUnicode(text[:start] + tag.text + text[end:])

(упрощено из этого использования.)

спасибо @dan04!

Ответ 3

Мне прежде всего нужно было точно проверить длину. Следовательно, эта функция, которая правильно возвращает длину кодового номера любой строки unicode, является ли интерпретатор узким или широким. Если данные используют два суррогатных литерала вместо одной кодовой точки \U в широко распространенном интерпретаторе, возвращаемая длина кодового номера будет учитывать это до тех пор, пока суррогаты будут использоваться "правильно", т.е. Как узкопостроенные интерпретатор будет использовать их.

invoke = lambda f: f()  # trick borrowed from Node.js

@invoke
def ulen():
  testlength = len(u'\U00010000')
  assert (testlength == 1) or (testlength == 2)
  if testlength == 1:  # "wide" interpreters
    def closure(data):
      u'returns the number of Unicode code points in a unicode string'
      return len(data.encode('UTF-16BE').decode('UTF-16BE'))
  else:  # "narrow" interpreters
    def filt(c):
      ordc = ord(c)
      return (ordc >= 55296) and (ordc < 56320)
    def closure(data):
      u'returns the number of Unicode code points in a unicode string'
      return len(data) - len(filter(filt, data))
  return closure  # ulen() body is therefore different on narrow vs wide builds

Тестовый чехол, проходит по узким и широким строкам:

class TestUlen(TestCase):

  def test_ulen(self):
    self.assertEquals(ulen(u'\ud83d\udc4d'), 1)
    self.assertEquals(ulen(u'\U0001F44D'), 1)