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

Байты в строке юникода Python

В 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, а затем дополнительного регулярного выражения строкового представления в Юникоде. Есть ли более чистый способ?

4b9b3361

Ответ 1

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

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

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

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

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

Ответ 2

(В ответ на комментарии выше): этот код преобразует все, что выглядит как 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ää  

Ответ 3

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

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.

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

Ответ 4

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

u'\xd0' == u'\u00d0' 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 и конвертировал их в символы, а затем расшифровывал их.

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

Ответ 5

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

  1. Соответствует символам Unicode <U + 0100, которые напоминают действительные последовательности UTF-8 (ref: RFC 3629).
  2. Кодирует последовательность Unicode в эквивалентную последовательность латинского-1 байта.
  3. Декодирует последовательность, используя UTF-8, в Unicode.
  4. Заменяет исходную последовательность, похожую на UTF-8, на соответствующий символ Юникода.

Обратите внимание, что это все равно будет соответствовать последовательности 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 '\ u0420\u0443\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\xfa\xfb\xfc\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 [\] ^ _ "АБВГДЕЖЗИКЛМНОПРСТУФХЧШЭЮЯ {|} ~\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\xfa\xfb\xfc\xfd\xfe\xff\x7f \ x80\u07ff\u0800\U00010000\U0010ffff '