Как перебирать символы Unicode в Python 3? - PullRequest
13 голосов
/ 21 сентября 2011

Мне нужно пройти по строке Python по одному символу за раз, но простой цикл «for» вместо этого дает мне кодовые единицы UTF-16:

str = "abc\u20ac\U00010302\U0010fffd"
for ch in str:
    code = ord(ch)
    print("U+{:04X}".format(code))

Это печатает:

U+0061
U+0062
U+0063
U+20AC
U+D800
U+DF02
U+DBFF
U+DFFD

когда то, что я хотел, было:

U+0061
U+0062
U+0063
U+20AC
U+10302
U+10FFFD

Есть ли способ заставить Python дать мне последовательность кодовых точек Unicode, независимо от того, как строка фактически закодирована под капотом?Я здесь тестирую на Windows, но мне нужен код, который будет работать где угодно.Он должен работать только на Python 3, меня не волнует Python 2.x.

Лучшее, что я смог придумать, это:

import codecs
str = "abc\u20ac\U00010302\U0010fffd"
bytestr, _ = codecs.getencoder("utf_32_be")(str)
for i in range(0, len(bytestr), 4):
    code = 0
    for b in bytestr[i:i + 4]:
        code = (code << 8) + b
    print("U+{:04X}".format(code))

Но я надеюсь, что есть более простой способ.

(Педантичные придирки к точной терминологии Unicode будут безжалостно избиты по голове ключом к четырем. Я думаю, я ясно дал понять, что я 'm после этого, пожалуйста, не тратьте место с аргументами "но UTF-16 - это тоже технически Unicode".

Ответы [ 3 ]

7 голосов
/ 21 сентября 2011

В Python 3.2.1 с узкой сборкой Unicode:

PythonWin 3.2.1 (default, Jul 10 2011, 21:51:15) [MSC v.1500 32 bit (Intel)] on win32.
Portions Copyright 1994-2008 Mark Hammond - see 'Help/About PythonWin' for further copyright information.
>>> import sys
>>> sys.maxunicode
65535

Что вы обнаружили (кодировка UTF-16):

>>> s = "abc\u20ac\U00010302\U0010fffd"
>>> len(s)
8
>>> for c in s:
...     print('U+{:04X}'.format(ord(c)))
...     
U+0061
U+0062
U+0063
U+20AC
U+D800
U+DF02
U+DBFF
U+DFFD

Способ обойти это:

>>> import struct
>>> s=s.encode('utf-32-be')
>>> struct.unpack('>{}L'.format(len(s)//4),s)
(97, 98, 99, 8364, 66306, 1114109)
>>> for i in struct.unpack('>{}L'.format(len(s)//4),s):
...     print('U+{:04X}'.format(i))
...     
U+0061
U+0062
U+0063
U+20AC
U+10302
U+10FFFD

Обновление для Python 3.3:

Теперь все работает так, как ожидает ОП:

>>> s = "abc\u20ac\U00010302\U0010fffd"
>>> len(s)
6
>>> for c in s:
...     print('U+{:04X}'.format(ord(c)))
...     
U+0061
U+0062
U+0063
U+20AC
U+10302
U+10FFFD
3 голосов
/ 21 сентября 2011

Если вы создаете строку как объект в кодировке Юникод, она должна иметь возможность автоматически разрывать символ за раз. E.g.:

Python 2.6:

s = u"abc\u20ac\U00010302\U0010fffd"   # note u in front!
for c in s:
    print "U+%04x" % ord(c)

Я получил:

U+0061
U+0062
U+0063
U+20ac
U+10302
U+10fffd

Python 3.2:

s = "abc\u20ac\U00010302\U0010fffd"
for c in s:
    print ("U+%04x" % ord(c))

У меня сработало:

U+0061
U+0062
U+0063
U+20ac
U+10302
U+10fffd

Кроме того, я нашел эту ссылку , которая объясняет, что поведение работает правильно. Если строка взята из файла и т. Д., То, скорее всего, сначала ее нужно будет декодировать.

Update :

Я нашел проницательное объяснение здесь . Внутренний размер представления Unicode является параметром времени компиляции, и если вы работаете с «широкими» символами за пределами 16-битной плоскости, вам нужно будет самостоятельно собрать python, чтобы снять ограничение, или использовать один из обходных путей на этой странице. Очевидно, многие дистрибутивы Linux делают это для вас уже, как я встречал выше.

3 голосов
/ 21 сентября 2011

Python обычно хранит значения Юникода внутри себя как UCS2.UTF-16 представление символа UTF-32 \ U00010302 - \ UD800 \ UDF02, поэтому вы получили такой результат.

Тем не менее, есть некоторые сборки Python, использующие UCS4, но эти сборки не совместимыдруг с другом.

Взгляните здесь .

Py_UNICODE Этот тип представляет тип хранилища, который используется внутри Python как основа для хранения ординалов Unicode.Стандартные сборки Python используют 16-битный тип для Py_UNICODE и хранят значения Unicode внутри как UCS2.Также возможно собрать версию Python для UCS4 (самые последние дистрибутивы Linux поставляются с сборками Python для UCS4).Эти сборки затем используют 32-битный тип для Py_UNICODE и хранят данные Unicode внутри как UCS4.На платформах, где wchar_t доступен и совместим с выбранным вариантом сборки Python Unicode, Py_UNICODE является псевдонимом typedef для wchar_t для улучшения совместимости с собственной платформой.На всех других платформах Py_UNICODE является псевдонимом typedef для коротких без знака (UCS2) или длинных без знака (UCS4).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...