Двойной декодирование юникода в python
Я работаю против приложения, которое, похоже, хочет вернуться, что я считаю двойным кодированным строкой UTF-8.
Я отправляю строку u'XüYß'
, закодированную с использованием UTF-8, становясь X\u00fcY\u00df
(равным X\xc3\xbcY\xc3\x9f
).
Сервер должен просто повторить то, что я ему отправил, но возвращает следующее: X\xc3\x83\xc2\xbcY\xc3\x83\xc2\x9f
(должно быть X\xc3\xbcY\xc3\x9f
). Если я декодирую его с помощью str.decode('utf-8')
становится u'X\xc3\xbcY\xc3\x9f'
, который выглядит как... unicode-строка, содержащая исходную строку, кодированную с использованием UTF-8.
Но Python не позволит мне декодировать строку unicode, не перекодируя ее сначала - что по какой-то причине не срабатывает, что ускользает от меня:
>>> ret = 'X\xc3\x83\xc2\xbcY\xc3\x83\xc2\x9f'.decode('utf-8')
>>> ret
u'X\xc3\xbcY\xc3\x9f'
>>> ret.decode('utf-8')
# Throws UnicodeEncodeError: 'ascii' codec can't encode ...
Как убедить Python повторно декодировать строку? - и/или есть (практический) способ отладки, что на самом деле в строках, без его передачи, хотя используется все неявное преобразование print
?
(И да, я сообщил об этом поведении с разработчиками серверной части.)
Ответы
Ответ 1
ret.decode()
пытается неявно кодировать ret
с системным кодированием - в вашем случае ascii.
Если вы явно кодируете строку unicode, вы должны быть в порядке. Существует встроенная кодировка, которая делает то, что вам нужно:
>>> 'X\xc3\xbcY\xc3\x9f'.encode('raw_unicode_escape').decode('utf-8')
'XüYß'
Действительно, .encode('latin1')
(или cp1252) будет в порядке, потому что это то, что сервер почти исключительно использует. Кодек raw_unicode_escape
просто даст вам что-то узнаваемое в конце, а не поднимает исключение:
>>> '€\xe2\x82\xac'.encode('raw_unicode_escape').decode('utf8')
'\\u20ac€'
>>> '€\xe2\x82\xac'.encode('latin1').decode('utf8')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'latin-1' codec can't encode character '\u20ac' in position 0: ordinal not in range(256)
Если вы столкнетесь с подобными смешанными данными, вы можете снова использовать кодек, чтобы нормализовать все:
>>> '€\xe2\x82\xac'.encode('raw_unicode_escape').decode('utf8')
'\\u20ac€'
>>> '\\u20ac€'.encode('raw_unicode_escape')
b'\\u20ac\\u20ac'
>>> '\\u20ac€'.encode('raw_unicode_escape').decode('raw_unicode_escape')
'€€'
Ответ 2
То, что вы хотите, - это кодировка, в которой кодовая точка Юникода X закодирована в одно и то же значение байта X. Для кодовых точек внутри 0-255 вы имеете это в кодировке latin-1:
def double_decode(bstr):
return bstr.decode("utf-8").encode("latin-1").decode("utf-8")
Ответ 3
Не используйте это! Используйте @hop solution.
Мой неприятный взлом: (съеживаешься, но тихо. Это не моя вина, это ошибка разработчиков сервера)
def double_decode_unicode(s, encoding='utf-8'):
return ''.join(chr(ord(c)) for c in s.decode(encoding)).decode(encoding)
Затем
>>> double_decode_unicode('X\xc3\x83\xc2\xbcY\xc3\x83\xc2\x9f')
u'X\xfcY\xdf'
>>> print _
XüYß
Ответ 4
Вот немного script, которые могут вам помочь, doubledecode.py -
https://gist.github.com/1282752