Специфичная для платформы семантика 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)