Подтвердить что ты не робот

Как перебирать символы Unicode в Python 3?

Мне нужно пройти через один символ 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))

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

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

4b9b3361

Ответ 1

В 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:

Теперь он работает так, как ожидает OP:

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

Ответ 2

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

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

Посмотрите здесь.

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

Ответ 3

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

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

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

Обновление

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