UTF-8编码的“损坏”unicode字符串?

3 投票
2 回答
3363 浏览
提问于 2025-04-15 20:14

我这两天一直在研究unicode和它在Python中的实现,感觉我开始明白它是怎么回事了。为了确认我的理解是否正确,我想问一下我对当前问题的假设。

在Django中,表单给我提供了unicode字符串,我怀疑这些字符串是“坏的”。在Python中,unicode字符串应该用UTF-8编码,对吧?当我在文本框里输入字符串“fähre”时,浏览器在POST请求中发送的是“f%c3%a4hre”(我用wireshark检查过)。但是当我通过form.cleaned_data获取这个值时,我得到的是字符串u'f\xa4hre'(注意这是一个unicode字符串)。据我理解,这个字符串是用ISO-8859-1编码的unicode字符串,这是不正确的。正确的字符串应该是u'f\xc3\xa4hre',这才是用UTF-8编码的unicode字符串。这是Django的bug,还是我对它的理解有问题?

为了解决这个问题,我写了一个函数来处理Django表单的任何文本输入:

def fix_broken_unicode(s):
    return unicode(s.encode(u'utf-8'), u'iso-8859-1')

这个函数做了

>>> fix_broken_unicode(u'f\xa4hre')
u'f\xc3\xa4hre'

我觉得这看起来不是很优雅,但把Django的settings.DEFAULT_CHARSET设置为'utf-8'并没有帮助,其他方法也没有。我想在整个应用中都使用unicode,这样以后就不会出现奇怪的错误,但显然仅仅用u'...'来标记所有字符串是不够的。

编辑:根据Dirk和sth的回答,我现在会把字符串原样保存到数据库中。真正的问题是我试图对这些字符串进行url编码,以便用作Twitter API等的输入。不过在GET或POST请求中,显然是需要UTF-8编码的,而标准的urllib.urlencode()函数并没有正确处理这个(会抛出异常)。你可以看看我在pastebin上的解决方案,也欢迎你对它发表评论。

2 个回答

1

其实不是完全正确:在解码之后,unicode 字符串是 unicode,这意味着它可能包含一些代码超过 255 的字符。解释器如何表示这些字符取决于平台,但现在通常使用的字符元素宽度至少是 16 位。ISO-8859-1 是 unicode 的一个子集。因此,字符串 u'f\xa4hre' 实际上是正确的——\xa4 是一种显示上的表现,因为 Python 不知道在控制台上包含超出某个范围的字符是否安全。

UTF-8 是一种 传输编码,也就是说,它是一种特殊的方式来写 unicode 数据,使其可以存储在每个字符/字节宽度为 8 位的“通道”中。为了计算一个 unicode 字符串的正确“外部”(或传输)编码,你可以使用 encode 方法,并传入你想要的表示方式。它会返回一个正确编码的 字节字符串(与 unicode 字符串不同)。

反向转换是 decode,它接收一个 字节字符串 和一个编码名称,然后返回一个 unicode 字符字符串

4

u'f\xa4hre'是一个unicode字符串,它并没有被编码成其他格式。unicode代码点0xa4对应的字符是ä。其实,ä在ISO-8859-1编码中也可以表示为字节0xa4,但这并不重要。

这个unicode字符串可以包含任何unicode字符,而不需要以某种方式进行编码。比如,轮渡在unicode中表示为u'\u8f6e\u6e21',这只是两个unicode代码点而已。如果用UTF-8编码,它会变得更长,表示为'\xe8\xbd\xae\xe6\xb8\xa1'

所以你不需要去修复编码问题,你看到的只是unicode字符串的内部表示方式。

撰写回答