理解 decode() 和 encode() 的 Unicode

4

我就是无法理解python2.7中的decode()encode()函数是如何工作的。

我尝试了以下语句:

>>> s = u'abcd'
>>> s.encode('utf8')
'abcd'
>>> s.encode('utf16')
'\xff\xfea\x00b\x00c\x00d\x00'
>>> s.encode('utf32')
'\xff\xfe\x00\x00a\x00\x00\x00b\x00\x00\x00c\x00\x00\x00d\x00\x00\x00'

直到这里,我认为已经很清楚了;encode()函数将unicode编码转换为相应的utf-8/16/32字节字符串。
但是当我编写以下代码:
>>> s.decode('utf8')
u'abcd'
>>> s.decode('utf16')
u'\u6261\u6463'
>>> s.decode('utf32')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.7/encodings/utf_32.py", line 11, in decode
    return codecs.utf_32_decode(input, errors, True)
UnicodeDecodeError: 'utf32' codec can't decode bytes in position 0-3: codepoint not in range(0x110000)

为什么在unicode类型中使用decode()的含义是什么?为什么第一个(使用utf8)可以工作,而后面的不行?这是因为Python内部使用utf-8存储Unicode字符串吗?

最后一件事:

>>> s2 = '≈'
>>> s2
'\xe2\x89\x88'

在幕后会发生什么?'≈'不是ASCII字符,那么Python是否会使用编码sys.getfilesystemencoding()隐式地进行转换?


1
Martijn的回答解释了这种情况。值得注意的是,在Python 3中,这种混淆已经被澄清。在Python 3中,unicode对象没有decode方法,而bytes对象没有encode方法,因此您不能意外地在错误的方向上进行编码/解码。 - BrenBarn
1
你能否不要不断扩大你的问题? - Martijn Pieters
1个回答

6
你正在对一个unicode字符串调用decode方法。Python会聪明地先使用默认的ASCII编解码器将该字符串编码成实际的字节,以便你可以进行解码。你不能对Unicode数据本身进行解码,它已经被解码过了。
然后,由于这些字节不是有效的UTF-32数据,所以解码失败了。字节串'abcd'可以作为UTF-8进行解码,因为ASCII是UTF-8的子集。先编码成ASCII再解码成UTF-8会产生相同的信息。解码成UTF-16恰好能够运行,因为你提供了4个十六进制值为0x61、0x62、0x63和0x64的字节(字符abcd的ASCII值),而这些字节可以解码为UTF-16小端的\u6261\u6463。但是,在UTF-32编码系统中,这4个字节没有有效的解码方式。
如果s包含的数据不能首先编码为ASCII,则会引发UnicodeEncodeError异常;请注意名称中的Encode
>>> u'åßç'.decode('utf8')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/mj/Development/venvs/stackoverflow-2.7/lib/python2.7/encodings/utf_8.py", line 16, in decode
    return codecs.utf_8_decode(input, errors, True)
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-2: ordinal not in range(128)

由于将隐式编码转换为字节字符串失败了。

在Python 3中,unicode对象已被重命名为str,并且str.decode()方法已被删除,以防止这种混淆。只剩下str.encode()。Python的str类型已被bytes类型替换,后者只有一个bytes.decode()方法。

您的第二个示例表明您正在交互式终端或控制台中使用Python解释器。 Python从终端接收到UTF-8字节,并将这些字节存储在一个字节串中。如果您使用了unicode文本,Python会自动使用为您的终端声明的编码解码这些字节;您可以查看sys.stdin.encoding来查看Python检测到的编码:

>>> import sys
>>> sys.stdin.encoding
'UTF-8'
>>> s = '≈'
>>> s
'\xe2\x89\x88'
>>> s = u'≈'
>>> s
u'\u2248'
>>> print s
≈

反过来说,当打印 sys.stdout.encoding 时,编解码器会自动将Unicode字符串编码为终端使用的编解码器,然后再次解释这些字节以在屏幕上显示正确的字形。
如果您不是在Python交互式解释器中工作,而是在处理Python源文件,则要使用的编解码器取决于PEP-263 Python源代码编码声明,因为Python 2会将字节默认解码为ASCII。 sys.getfilesystemencoding() 与所有这些都没有关系;它告诉您Python认为您的文件系统元数据使用的编码方式;例如目录中的文件名。当您使用unicode路径进行文件系统相关调用,如os.listdir()时,该值会被使用。

答案的最后一部分对我来说相当令人困惑:s = '≈',然后是 '\xe2\x89\x88',因为 Python 将字符作为 utf-8 字符接收,而我无法从 sys.stdin.encoding 中看到它,该函数返回 utf-8。现在如果我使用了s = u'≈',我不明白你想说什么。 - zer0uno
@antox: u'≈'.encode('utf8') == '\xe2\x89\x88'的UTF-8编码是这3个字节。 - Martijn Pieters
@antox:不,str 存储的是字节。unicode 存储的是 Unicode 编码,但是你需要先解码字节。Python 会使用 sys.stdin.encoding 来完成后者操作。 - Martijn Pieters
@antox:你的终端始终会发送字节。 - Martijn Pieters
最后一件事;当打印名称 s (s = u'≈') 时,隐式地好像是将 s.encode(sys.stdout.encoding) 的字节发送到监视器,然后显示出来,对吗? - zer0uno
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接