Как перебирать символы 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 слишком" аргументом".
Ответы
Ответ 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 делают это для вас уже, как я встречал выше.