UTF-8编码的“损坏”unicode字符串?
我这两天一直在研究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 个回答
其实不是完全正确:在解码之后,unicode 字符串是 unicode,这意味着它可能包含一些代码超过 255 的字符。解释器如何表示这些字符取决于平台,但现在通常使用的字符元素宽度至少是 16 位。ISO-8859-1 是 unicode 的一个子集。因此,字符串 u'f\xa4hre'
实际上是正确的——\xa4
是一种显示上的表现,因为 Python 不知道在控制台上包含超出某个范围的字符是否安全。
UTF-8 是一种 传输编码,也就是说,它是一种特殊的方式来写 unicode 数据,使其可以存储在每个字符/字节宽度为 8 位的“通道”中。为了计算一个 unicode 字符串的正确“外部”(或传输)编码,你可以使用 encode
方法,并传入你想要的表示方式。它会返回一个正确编码的 字节字符串(与 unicode 字符串不同)。
反向转换是 decode
,它接收一个 字节字符串 和一个编码名称,然后返回一个 unicode 字符字符串。
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字符串的内部表示方式。