142

从 Python 2.6 外壳:

>>> import sys
>>> print sys.getdefaultencoding()
ascii
>>> print u'\xe9'
é
>>> 

我希望在 print 语句之后会有一些乱码或错误,因为“é”字符不是 ASCII 的一部分,而且我没有指定编码。我想我不明白 ASCII 作为默认编码是什么意思。

编辑

我将编辑移至“答案”部分并按照建议接受了它。

4

6 回答 6

107

感谢各种回复的点点滴滴,我想我们可以拼凑一个解释。

通过尝试打印 unicode 字符串 u'\xe9',Python 隐式尝试使用当前存储在 sys.stdout.encoding 中的编码方案对该字符串进行编码。Python 实际上是从启动它的环境中获取这个设置的。如果它无法从环境中找到正确的编码,那么它才会恢复为默认的 ASCII。

例如,我使用编码默认为 UTF-8 的 bash shell。如果我从它启动 Python,它会选择并使用该设置:

$ python

>>> import sys
>>> print sys.stdout.encoding
UTF-8

让我们暂时退出 Python shell 并使用一些虚假编码设置 bash 的环境:

$ export LC_CTYPE=klingon
# we should get some error message here, just ignore it.

然后再次启动 python shell 并验证它确实恢复为默认的 ascii 编码。

$ python

>>> import sys
>>> print sys.stdout.encoding
ANSI_X3.4-1968

答对了!

如果您现在尝试在 ascii 之外输出一些 unicode 字符,您应该会收到一条不错的错误消息

>>> print u'\xe9'
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe9' 
in position 0: ordinal not in range(128)

让我们退出 Python 并丢弃 bash shell。

我们现在将观察 Python 输出字符串后会发生什么。为此,我们将首先在图形终端(我使用 Gnome 终端)中启动一个 bash shell,然后我们将终端设置为使用 ISO-8859-1 aka latin-1 解码输出(图形终端通常具有设置字符的选项在其下拉菜单之一中进行编码)。请注意,这不会改变实际的 shell 环境的编码,它只会改变终端本身对给出的输出进行解码的方式,有点像网络浏览器。因此,您可以独立于 shell 环境更改终端的编码。然后让我们从 shell 启动 Python 并验证 sys.stdout.encoding 是否设置为 shell 环境的编码(对我来说是 UTF-8):

$ python

>>> import sys

>>> print sys.stdout.encoding
UTF-8

>>> print '\xe9' # (1)
é
>>> print u'\xe9' # (2)
é
>>> print u'\xe9'.encode('latin-1') # (3)
é
>>>

(1) python 按原样输出二进制字符串,终端接收它并尝试将其值与 latin-1 字符映射匹配。在 latin-1 中,0xe9 或 233 产生字符“é”,这就是终端显示的内容。

(2) python 尝试使用 sys.stdout.encoding 中当前设置的任何方案对 Unicode 字符串进行隐式编码,在本例中为“UTF-8”。经过 UTF-8 编码后,生成的二进制字符串是 '\xc3\xa9'(见后面的解释)。终端这样接收流并尝试使用 latin-1 解码 0xc3a9,但 latin-1 从 0 变为 255,因此一次只解码 1 个字节的流。0xc3a9 有 2 个字节长,因此 latin-1 解码器将其解释为 0xc3 (195) 和 0xa9 (169) 并产生 2 个字符:Ã 和 ©。

(3) python 用 latin-1 方案对 unicode 码点 u'\xe9' (233) 进行编码。结果 latin-1 代码点的范围是 0-255,并且指向与该范围内的 Unicode 完全相同的字符。因此,该范围内的 Unicode 代码点在以 latin-1 编码时将产生相同的值。因此以 latin-1 编码的 u'\xe9' (233) 也会产生二进制字符串 '\xe9'。终端接收该值并尝试在 latin-1 字符映射上匹配它。就像案例(1)一样,它产生“é”,这就是显示的内容。

现在让我们从下拉菜单中将终端的编码设置更改为 UTF-8(就像您更改 Web 浏览器的编码设置一样)。无需停止 Python 或重新启动 shell。终端的编码现在匹配 Python 的。让我们再次尝试打印:

>>> print '\xe9' # (4)

>>> print u'\xe9' # (5)
é
>>> print u'\xe9'.encode('latin-1') # (6)

>>>

(4) python 按原样输出二进制字符串。终端尝试使用 UTF-8 解码该流。但是 UTF-8 不理解值 0xe9(请参阅后面的解释),因此无法将其转换为 unicode 代码点。未找到代码点,未打印字符。

(5) python 尝试使用 sys.stdout.encoding 中的任何内容对 Unicode 字符串进行隐式编码。仍然是“UTF-8”。生成的二进制字符串是 '\xc3\xa9'。终端接收流并尝试也使用 UTF-8 解码 0xc3a9。它产生返回码值 0xe9 (233),它在 Unicode 字符映射上指向符号“é”。终端显示“é”。

(6) python 用 latin-1 编码 unicode 字符串,它产生一个具有相同值 '\xe9' 的二进制字符串。同样,对于终端,这与案例 (4) 几乎相同。

结论: - Python 将非 unicode 字符串输出为原始数据,而不考虑其默认编码。如果终端当前的编码与数据匹配,终端恰好会显示它们。- Python 使用 sys.stdout.encoding 中指定的方案编码后输出 Unicode 字符串。- Python 从 shell 环境中获取该设置。- 终端根据自己的编码设置显示输出。- 终端的编码独立于外壳的。


有关 unicode、UTF-8 和 latin-1 的更多详细信息:

Unicode 基本上是一个字符表,其中一些键(代码点)通常被分配以指向一些符号。例如,按照惯例,已确定键 0xe9 (233) 是指向符号“é”的值。ASCII 和 Unicode 使用从 0 到 127 的相同码位,latin-1 和 Unicode 从 0 到 255 也是如此。即 ASCII 中的 0x41 指向 'A',latin-1 和 Unicode 中的 0xc8 指向 'Ü' latin-1 和 Unicode,0xe9 指向 latin-1 和 Unicode 中的 'é'。

在使用电子设备时,Unicode 代码点需要一种有效的电子方式来表示。这就是编码方案的意义所在。存在各种 Unicode 编码方案(utf7、UTF-8、UTF-16、UTF-32)。最直观和直接的编码方法是简单地使用 Unicode 映射中的代码点值作为其电子形式的值,但 Unicode 目前有超过一百万个代码点,这意味着其中一些需要 3 个字节表达。为了有效地处理文本,1 对 1 映射将是相当不切实际的,因为它要求所有代码点存储在完全相同的空间中,每个字符至少 3 个字节,而不管它们的实际需要。

大多数编码方案在空间要求方面都有缺点,最经济的方案并不涵盖所有 unicode 码位,例如 ascii 仅涵盖前 128 个,而 latin-1 涵盖前 256 个。其他尝试更全面的最终也浪费,因为它们需要比必要更多的字节,即使对于常见的“便宜”字符也是如此。例如,UTF-16 每个字符至少使用 2 个字节,包括 ascii 范围内的那些('B' 是 65,在 UTF-16 中仍然需要 2 个字节的存储空间)。UTF-32 更加浪费,因为它将所有字符存储在 4 个字节中。

UTF-8 恰好巧妙地解决了这个难题,其方案能够存储具有可变字节空间数量的代码点。作为其编码策略的一部分,UTF-8 将代码点与指示(可能对解码器)它们的空间要求和边界的标志位相结合。

ascii 范围 (0-127) 中 unicode 代码点的 UTF-8 编码:

0xxx xxxx  (in binary)
  • x 显示为在编码期间“存储”代码点而保留的实际空间
  • 前导 0 是一个标志,向 UTF-8 解码器指示此代码点仅需要 1 个字节。
  • 编码时,UTF-8 不会更改该特定范围内代码点的值(即 UTF-8 编码的 65 也是 65)。考虑到 Unicode 和 ASCII 在同一范围内也兼容,顺便说一下,UTF-8 和 ASCII 在该范围内也兼容。

例如,'B' 的 Unicode 代码点是二进制的 '0x42' 或 0100 0010(正如我们所说,它在 ASCII 中是相同的)。用 UTF-8 编码后变成:

0xxx xxxx  <-- UTF-8 encoding for Unicode code points 0 to 127
*100 0010  <-- Unicode code point 0x42
0100 0010  <-- UTF-8 encoded (exactly the same)

127 以上的 Unicode 代码点的 UTF-8 编码(非 ascii):

110x xxxx 10xx xxxx            <-- (from 128 to 2047)
1110 xxxx 10xx xxxx 10xx xxxx  <-- (from 2048 to 65535)
  • 前导位“110”向 UTF-8 解码器指示以 2 个字节编码的代码点的开头,而“1110”表示 3 个字节,11110 表示 4 个字节,依此类推。
  • 内部“10”标志位用于表示内部字节的开始。
  • 再次,x 标记编码后存储 Unicode 代码点值的空间。

例如,'é' Unicode 代码点是 0xe9 (233)。

1110 1001    <-- 0xe9

当 UTF-8 对该值进行编码时,判断该值大于 127 且小于 2048,因此应编码为 2 个字节:

110x xxxx 10xx xxxx   <-- UTF-8 encoding for Unicode 128-2047
***0 0011 **10 1001   <-- 0xe9
1100 0011 1010 1001   <-- 'é' after UTF-8 encoding
C    3    A    9

UTF-8 编码后的 0xe9 Unicode 码位变为 0xc3a9。这正是终端接收它的方式。如果您的终端设置为使用 latin-1(非 unicode 传统编码之一)解码字符串,您将看到 é,因为恰好 latin-1 中的 0xc3 指向 à 和 0xa9 指向 ©。

于 2014-02-23T13:09:38.680 回答
27

当 Unicode 字符打印到标准输出时,sys.stdout.encoding使用。假定存在非 Unicode 字符,sys.stdout.encoding并且仅将其发送到终端。在我的系统(Python 2)上:

>>> import unicodedata as ud
>>> import sys
>>> sys.stdout.encoding
'cp437'
>>> ud.name(u'\xe9') # U+00E9 Unicode codepoint
'LATIN SMALL LETTER E WITH ACUTE'
>>> ud.name('\xe9'.decode('cp437')) 
'GREEK CAPITAL LETTER THETA'
>>> '\xe9'.decode('cp437') # byte E9 decoded using code page 437 is U+0398.
u'\u0398'
>>> ud.name(u'\u0398')
'GREEK CAPITAL LETTER THETA'
>>> print u'\xe9' # Unicode is encoded to CP437 correctly
é
>>> print '\xe9'  # Byte is just sent to terminal and assumed to be CP437.
Θ

sys.getdefaultencoding()仅在 Python 没有其他选项时使用。

请注意,Python 3.6 或更高版本会忽略 Windows 上的编码并使用 Unicode API 将 Unicode 写入终端。没有 UnicodeEncodeError 警告,如果字体支持,则会显示正确的字符。即使字体支持它,字符仍然可以从终端剪切并粘贴到具有支持字体的应用程序中,并且它是正确的。升级!

于 2010-04-08T02:47:04.853 回答
8

Python REPL 尝试从您的环境中获取要使用的编码。如果它发现一些理智的东西,那么这一切都只是工作。当它无法弄清楚发生了什么时,它就会出错。

>>> print sys.stdout.encoding
UTF-8
于 2010-04-08T00:07:43.247 回答
4

通过输入显式 Unicode 字符串来指定编码。比较不使用u前缀的结果。

>>> import sys
>>> sys.getdefaultencoding()
'ascii'
>>> '\xe9'
'\xe9'
>>> u'\xe9'
u'\xe9'
>>> print u'\xe9'
é
>>> print '\xe9'

>>> 

在这种情况下,\xe9Python 假定您的默认编码(Ascii),因此打印......一些空白。

于 2010-04-08T00:08:07.410 回答
0

这个对我有用:

import sys
stdin, stdout = sys.stdin, sys.stdout
reload(sys)
sys.stdin, sys.stdout = stdin, stdout
sys.setdefaultencoding('utf-8')
于 2014-08-12T09:57:56.613 回答
0

根据Python 默认/隐式字符串编码和转换

  • printing时unicode,它是encoded 和<file>.encoding
    • encoding未设置时,将unicode隐式转换为str(因为它的编解码器是sys.getdefaultencoding(),即ascii任何国家字符都会导致 a UnicodeEncodeError
    • 对于标准流,encoding从环境推断。它通常设置 fottty流(来自终端的语言环境设置),但可能不会为管道设置
      • 因此 aprint u'\xe9'在输出到终端时可能会成功,如果被重定向则可能会失败。一种解决方案是在ingencode()之前使用所需编码的字符串。print
  • printingstr时,字节按原样发送到流。终端显示的字形将取决于其区域设置。
于 2018-05-01T06:29:43.417 回答