специфичная для платформы семантика Unicode в Python 2.7 - PullRequest
2 голосов
/ 30 марта 2012

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 (без ответа)?
  • Оправдывает ли это сообщение об ошибке?Есть ли вероятность, что такой отчет об ошибке будет рассмотрен в 2.7?

Ответы [ 3 ]

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

В Ubuntu у вас есть "широкая" сборка Python , где строки - 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 голосов
/ 23 февраля 2017

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

Ответ

@ 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))

с открытым исходным кодом здесь , общественное достояние. пример использования:

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

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

спасибо @ dan04!

1 голос
/ 19 апреля 2012

Мне прежде всего нужно было точно проверить длину. Следовательно, эта функция корректно возвращает длину кодовой точки любой строки 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)
...