Байт в строке Python Юникода - PullRequest
33 голосов
/ 24 марта 2012

В Python 2 строки Unicode могут содержать как Unicode, так и байты:

a = u'\u0420\u0443\u0441\u0441\u043a\u0438\u0439 \xd0\xb5\xd0\xba'

Я понимаю, что это абсолютно не то, что нужно писать в своем собственном коде, но этострока, с которой мне приходится иметь дело.

Байты в приведенной выше строке: UTF-8 для ек (Unicode \u0435\u043a).

Моя цель - получить строку в Unicodeсодержащий все в Unicode, то есть Русский ек (\u0420\u0443\u0441\u0441\u043a\u0438\u0439 \u0435\u043a).

Кодирование его в UTF-8 дает

>>> a.encode('utf-8')
'\xd0\xa0\xd1\x83\xd1\x81\xd1\x81\xd0\xba\xd0\xb8\xd0\xb9 \xc3\x90\xc2\xb5\xc3\x90\xc2\xba'

, который затем декодируется из UTF-8, дает начальныйстрока с байтами в них, что нехорошо:

>>> a.encode('utf-8').decode('utf-8')
u'\u0420\u0443\u0441\u0441\u043a\u0438\u0439 \xd0\xb5\xd0\xba'

Я нашел хакерский способ решения проблемы, однако:

>>> repr(a)
"u'\\u0420\\u0443\\u0441\\u0441\\u043a\\u0438\\u0439 \\xd0\\xb5\\xd0\\xba'"
>>> eval(repr(a)[1:])
'\\u0420\\u0443\\u0441\\u0441\\u043a\\u0438\\u0439 \xd0\xb5\xd0\xba'
>>> s = eval(repr(a)[1:]).decode('utf8')
>>> s
u'\\u0420\\u0443\\u0441\\u0441\\u043a\\u0438\\u0439 \u0435\u043a'
# Almost there, the bytes are proper now but the former real-unicode characters
# are now escaped with \u's; need to un-escape them.
>>> import re
>>> re.sub(u'\\\\u([a-f\\d]+)', lambda x : unichr(int(x.group(1), 16)), s)
u'\u0420\u0443\u0441\u0441\u043a\u0438\u0439 \u0435\u043a' # Success!

Это отлично работает, но выглядит очень хакерски из-заего использование eval, repr, а затем дополнительное регулярное выражение в представлении строки Unicode.Есть ли способ чище?

Ответы [ 5 ]

22 голосов
/ 24 марта 2012

В Python 2 строки Unicode могут содержать как Unicode, так и байты:

Нет, они не могут. Они содержат символы Юникода.

В исходной строке \xd0 не является байтом, который является частью кодировки UTF-8. Это символ Unicode с кодовой точкой 208. u'\xd0' == u'\u00d0'. Просто случается, что repr для строк Unicode в Python 2 предпочитает представлять символы с \x экранированием, где это возможно (то есть кодовые точки <256). </p>

Нет способа посмотреть на строку и сказать, что байт \xd0 должен быть частью некоторого символа в кодировке UTF-8, или если он фактически обозначает этот символ Unicode сам по себе.

Однако, если вы предполагаете, что вы всегда можете интерпретировать эти значения как закодированные, вы можете попробовать написать что-то, что анализирует каждый символ по очереди (используйте ord для преобразования в целое число кодовой точки), декодирует символы <256 как UTF-8 и передает символы> = 256 такими, какими они были.

12 голосов
/ 24 марта 2012

(в ответ на комментарии выше): этот код преобразует все, что выглядит как utf8, и оставляет другие кодовые точки как есть:

a = u'\u0420\u0443\u0441 utf:\xd0\xb5\xd0\xba bytes:bl\xe4\xe4'

def convert(s):
    try:
        return s.group(0).encode('latin1').decode('utf8')
    except:
        return s.group(0)

import re
a = re.sub(r'[\x80-\xFF]+', convert, a)
print a.encode('utf8')   

Результат:

Рус utf:ек bytes:blää  
11 голосов
/ 24 марта 2012

Проблема в том, что ваша строка фактически не закодирована в определенной кодировке . Пример строки:

a = u'\u0420\u0443\u0441\u0441\u043a\u0438\u0439 \xd0\xb5\xd0\xba'

Смешивает внутреннее представление Python строк Unicode с текстом utf-8. Если мы просто рассмотрим «специальные» символы:

>>> orig = u'\u0435\u043a'
>>> bytes = u'\xd0\xb5\xd0\xba'
>>> print orig
ек
>>> print bytes
ек

Но вы говорите, bytes кодируется utf-8:

>>> print bytes.encode('utf-8')
ек
>>> print bytes.encode('utf-8').decode('utf-8')
ек

Неправильно! Но как насчет:

>>> bytes = '\xd0\xb5\xd0\xba'
>>> print bytes
ек
>>> print bytes.decode('utf-8')
ек

Hurray.

Итак. Что это значит для меня? Это значит, что вы (возможно) решаете не ту проблему. Вы должны спросить нас / попытаться выяснить, почему ваши строки в этой форме для начала и как ее избежать / исправить это до , когда вы все перепутаете.

5 голосов
/ 24 марта 2012

Вы уже получили ответ, но вот способ расшифровки UTF-8- как последовательностей Unicode, которые с меньшей вероятностью декодируют последовательности Unicode latin-1 по ошибке.Функция re.sub:

  1. Соответствует символам Unicode RFC 3629 ).
  2. КодируетПоследовательность Unicode в ее эквивалентную последовательность байтов latin-1.
  3. Декодирует последовательность, используя UTF-8, обратно в Unicode.
  4. Заменяет исходную последовательность в стиле UTF-8 на соответствующий символ Unicode.

Обратите внимание, что это все равно может соответствовать последовательности Unicode, если просто правильные символы появляются рядом друг с другом, но это гораздо менее вероятно.

import re

# your example
a = u'\u0420\u0443\u0441\u0441\u043a\u0438\u0439 \xd0\xb5\xd0\xba'

# printable Unicode characters < 256.
a += ''.join(chr(n) for n in range(32,256)).decode('latin1')

# a few UTF-8 characters decoded as latin1.
a += ''.join(unichr(n) for n in [2**7-1,2**7,2**11-1,2**11]).encode('utf8').decode('latin1')

# Some non-BMP characters
a += u'\U00010000\U0010FFFF'.encode('utf8').decode('latin1')

print repr(a)

# Unicode codepoint sequences that resemble UTF-8 sequences.
p = re.compile(ur'''(?x)
    \xF0[\x90-\xBF][\x80-\xBF]{2} |  # Valid 4-byte sequences
        [\xF1-\xF3][\x80-\xBF]{3} |
    \xF4[\x80-\x8F][\x80-\xBF]{2} |

    \xE0[\xA0-\xBF][\x80-\xBF]    |  # Valid 3-byte sequences
        [\xE1-\xEC][\x80-\xBF]{2} |
    \xED[\x80-\x9F][\x80-\xBF]    |
        [\xEE-\xEF][\x80-\xBF]{2} |

    [\xC2-\xDF][\x80-\xBF]           # Valid 2-byte sequences
    ''')

def replace(m):
    return m.group(0).encode('latin1').decode('utf8')

print
print repr(p.sub(replace,a))

Вывод

u '\ u0203 \ u0441 \ u0441 \ u043a \ u0438 \ u0439 \ xd0 \ xb5 \ xd0 \ xba ! "# $% & \' () *.? +, - / 0123456789:; <=> @ ABCDEFGHIJKLMNOPQRSTUVWXYZ [\] ^ _ `АБВГДЕЖЗИКЛМНОПРСТУФХЧШЭЮЯ {|} ~ \ x7f \ x80 \ x81 \ x82 \ x83 \ x84 \ x85 \ x86 \ x87 \ x88 \ x89 \ X8A \x8b \ x8c \ x8d \ x8e \ x8f \ x90 \ x91 \ x92 \ x93 \ x94 \ x95 \ x96 \ x97 \ x98 \ x99 \ X9a \ X9b \ x9c \ x9d \ x9e \ x9f \ xa0 \ xa1 \ xa2 \ XA3 \xa4 \ xa5 \ xa6 \ xa7 \ xa8 \ xA9 \ хаа \ Xab \ XAC \ XAD \ XAE \ XAF \ XB0 \ XB1 \ XB2 \ xb3 \ XB4 \ XB5 \ XB6 \ xb7 \ XB8 \ xb9 \ Xba \ Xbb\ XBC \ XBD \ XBE \ XBF \ xc0 \ xc1 \ xc2 \ xc3 \ XC4 \ xc5 \ xc6 \ xc7 \ xc8 \ xc9 \ XCA \ XCB \ XCC \ XCD \ xce \ XCF \ xd0 \ xd1 \ XD2 \ XD3 \ xd4\ xd5 \ xd6 \ xd7 \ xd8 \ xd9 \ XDA \ XDB \ XDc \ XDD \ XDE \ XDF \ xe0 \ xe1 \ XE2 \ XE3 \ xe4 \ xe5 \ XE6 \ xe7 \ X Е8 \ xE9 \ хеа \ xeb \ XEC \ неподвижная\ Xee \ ХеР \ xf0 \ XF1 \ xf2 \ xf3 \ XF4 \ xf5 \ XF6 \ xf7 \ XF8 \ xf9 \ РФА \ XFB \ хк \ XFD \ XFE \ XFF \ x7f \ xc2 \ x80 \ XDF \ XBF \xe0 \ xa0 \ x80 \ xf0 \ x90 \ x80 \ x80 \ xf4 \ x8f \ xbf \ xbf '

u' \ u0420 \ u0443 \ u0441 \ u0441 \ u043a \ u0438 \ u0439 \ u0435 \ u043a ! "# $% & \ (() * +, -. / 0123456789:; <=>? @ ABCDEFGHIJKLMNOPQRSTUVWXYZ [\] ^ _` abcdefghijklmnopqrstuvwxyz {|} ~ \ x7f \ x80x82 \ x83 \ x84 \ x85 \ x86 \ x87 \ x88 \ x89 \ X8A \ x8b \ x8c \ x8d \ x8e \ x8f \ x90 \ x91 \ x92 \ x93 \ x94 \ x95 \ x96 \ x97 \ x98 \ x99 \ X9a \X9b \ x9c \ x9d \ x9e \ x9f \ xa0 \ xa1 \ xa2 \ XA3 \ xa4 \ xa5 \ xa6 \ xa7 \ xa8 \ xA9 \ хаа \ Xab \ XAC \ XAD \ XAE \ XAF \ XB0 \ XB1 \ XB2 \ xb3 \XB4 \ XB5 \ XB6 \ xb7 \ XB8 \ xb9 \ Xba \ Xbb \ XBC \ XBD \ XBE \ XBF \ xc0 \ xc1 \ xc2 \ xc3 \ XC4 \ xc5 \ xc6 \ xc7 \ xc8 \ xc9 \ XCA \ XCB \ XCC \XCD \ xce \ XCF \ xd0 \ xd1 \ XD2 \ XD3 \ xd4 \ xd5 \ xd6 \ xd7 \ xd8 \ xd9 \ XDA \ XDB \ XDc \ XDD \ XDE \ XDF \ xe0 \ xe1 \ XE2 \ XE3 \ xe4 \ xe5 \XE6 \ xe7 \ X Е8 \ xE9 \ хеа \ xeb \ XEC \ фиксировано \ Xee \ ХеР \ xf0 \ XF1 \ xf2 \ xf3 \ XF4 \xf5 \ xf6 \ xf7 \ xf8 \ xf9 \ xfa \ xfb \ xfc \ xfd \ xfe \ xff \ x7f \ x80 \ u07ff \ u0800 \ U00010000 \ U0010ffff '

5 голосов
/ 24 марта 2012

Вы должны преобразовать unichr s в chr s, а затем декодировать их.

u'\xd0' == u'\u00d0' is True

$ python
>>> import re
>>> a = u'\u0420\u0443\u0441\u0441\u043a\u0438\u0439 \xd0\xb5\xd0\xba'
>>> re.sub(r'[\000-\377]*', lambda m:''.join([chr(ord(i)) for i in m.group(0)]).decode('utf8'), a)
u'\u0420\u0443\u0441\u0441\u043a\u0438\u0439 \u0435\u043a'
  • r'[\000-\377]*' будет соответствоватьunichrs u'[\u0000-\u00ff]*'
  • u'\xd0\xb5\xd0\xba' == u'\u00d0\u00b5\u00d0\u00ba'
  • Вы используете utf8 закодированные байты в качестве кодовых точек Unicode ( это ПРОБЛЕМА )
  • Я решаюпроблема, делая вид, что эти ошибочные unichars являются соответствующими байтами
  • Я ищу все эти ошибочные unichars и преобразовываю их в символы, а затем декодирую их.

Если я ошибаюсь, пожалуйста,скажи мне.

...