带有非ASCII字符的字节字符串文字

7
显然,我可以在Python 2.7中做到这一点:
value = '國華'

看起来 Python 正在使用一种编码将字符串文字中的字符编码为字节字符串。那是什么编码?是在 sys.getdefaultencoding() 中定义的编码、源文件的编码还是其他编码?

谢谢。


1
这行代码是在源文件中还是在命令行上?另外,您是否真的想学习Python 2.7中编码的复杂性(它与2.6和3.x略有不同),还是只是试图通过在引号前加上u来解决问题? - abarnert
我实际上正在尝试学习Python 2.7中编码的复杂性。这是在源文件中,但我也想知道在命令提示符中是否有所不同。 - Flavien
1
Python 2.7 建议不要这样做,但如果您对 CPython 2.7 在这种情况下的行为感兴趣……源文件中引号内的字面字节(例如 '\xe5\x9c\x8b\xe8\x8f\xaf')被存储为字节字符串。由于源文件的编码声明可能与 sys.getdefaultencoding() 不同,因此您可能无法安全地解释它,但是字节确实存在。如果两个编码相同,并且终端的编码也相同,并且该编码可以处理这些字符,则甚至可以“打印值”并查看您期望的内容。这是您想要的吗,还是还有其他要求? - abarnert
2个回答

7

getdefaultencoding与源文件或终端的编码无关。它是用于将字节字符串隐式转换为Unicode字符串的编码,并且在Python 2.X中应始终为'ascii'(在Python 3.X中为'utf8')。

在Python 2.X中,如果您在未声明编码的脚本中使用您的代码行,则会产生错误:

SyntaxError: Non-ASCII character '\x87' in file ...

实际的非ASCII字符可能会有所不同,但是如果没有编码声明,它将无法工作。在Python 2.X中使用非ASCII字符必须要有一个编码声明,而且这个编码声明必须和源文件编码一致。例如:

# coding: utf8
value = '國華'

保存为cp936格式会产生以下结果:
SyntaxError: 'utf8' codec can't decode byte 0x87 in position 9: invalid start byte

当编码正确时,字节字符串中的字节确实是源文件中的内容,因此它将包含字符的编码字节。当Python解析Unicode字符串时,使用声明的源编码将字节解码为Unicode。请注意,在cp936控制台上打印UTF-8字节字符串和Unicode字符串时的区别:

# coding: utf8
value = '國華'
print value,repr(value)
value = u'國華'
print value,repr(value)

输出:

鍦嬭彲 '\xe5\x9c\x8b\xe8\x8f\xaf'
國華 u'\u570b\u83ef'

该字节字符串包含两个字符的3字节UTF-8编码,但由于cp936终端不理解该字节序列,因此显示不正确。Unicode打印正确,并且该字符串包含从源文件的UTF-8字节解码的Unicode代码点。

请注意,在声明和使用与终端匹配的编码时存在区别:

# coding: cp936
value = '國華'
print value,repr(value)
value = u'國華'
print value,repr(value)

输出:

國華 '\x87\xf8\xc8A'
國華 u'\u570b\u83ef'

字节字符串中的内容现在是两个字符('A'等于'\x41')的2字节cp936编码,并且由于终端了解cp936字节序列,因此正确显示。Unicode字符串包含与先前示例中相同的两个字符的Unicode代码点,因为使用声明的源编码将源字节序列解码为Unicode。
如果脚本具有正确的源编码声明并在文本中使用Unicode字符串,则无论终端编码如何,都将显示正确的字符1。如果终端不支持该字符,则会抛出UnicodeEncodeError而不是显示错误的字符。
最后注意:Python 2.X默认使用'ascii'编码,除非另行声明,并允许字节字符串中包含非ASCII字符(如果编码支持它们)。Python 3.X默认使用'utf8'编码(因此请确保以该编码保存或声明其他方式),并且不允许在字节字符串中包含非ASCII字符。 1如果终端字体支持该字符。
2如果终端编码支持该字符。

0
value = b'國華'

是毫无意义的(在Python 2.x中,b被隐含)- 你为什么想要一个包含字符字节字符串?Python只是以你的终端/编辑器所使用的编码方式重现字节。你需要的是一个字符字符串:

value = u'國華'

在源代码文件中(与交互式 shell 相对),不要忘记通过在文件开头添加以下行来声明编码
# -*- coding: utf-8 -*-

1
显然,Python 的设计者认为那并不是毫无意义的,因此他们将其视为有效。你没有真正回答我的问题,Python 在运行该行时使用哪种编码方式,我如何从代码中找到答案? - Flavien
当您在交互式 shell 中运行该行,或在带有编码声明的源文件中运行时,或者...什么? - abarnert
2
@Flavien,字节文字中的非ASCII字符是历史遗留问题。没有任何代码使它们有效,它们只是被意外接受了,这可以追溯到Python没有清晰的字节与字符字符串语义的时代。字节文字中的非ASCII字符不再有效;它们已经从Python 3中删除。当您运行该行时,Python不使用任何编码。该文件是一系列字节,字面值产生的字节对象的值也是如此。 - phihag
好的,所以它正在使用文件的编码。那是相当糟糕的设计。很遗憾没有人在使用Python 3。 - Flavien
1
@Flavien:你不必使用Python 3,只需在Python 2.7中使用Unicode字面量和字符串即可获得相同的效果。这里2.7引起的唯一问题是(a)允许您在未指定编码的字节字符串中存储非ASCII字符串,以及(b)在没有指定编码的情况下解释非ASCII输入。Python 3使这两个事情都非法;2.7已经弃用它们并且没有定义含义。如果您不做这些事情,就没有问题。 - abarnert

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