Python中的Unicode双重解码

17 投票
4 回答
15425 浏览
提问于 2025-04-16 07:32

我正在处理一个应用程序,它似乎总是返回我认为是双重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 来查看?

(是的,我已经向服务器端的开发者报告了这个问题。)

4 个回答

0

别用这个!@hop的解决方案

这是我一个很糟糕的解决办法:(让人 cringe!但要低调。这不是我的错,是服务器开发者的错)

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

你想要的是一种编码方式,其中Unicode的代码点X会被编码成相同的字节值X。对于0到255之间的代码点,拉丁-1编码就满足这个要求:

def double_decode(bstr):
    return bstr.decode("utf-8").encode("latin-1").decode("utf-8")
29

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')
'€€'

撰写回答