【问题标题】:Why does Python print unicode characters when the default encoding is ASCII?当默认编码为 ASCII 时,为什么 Python 会打印 unicode 字符?
【发布时间】:2011-02-05 12:07:29
【问题描述】:

从 Python 2.6 外壳:

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

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

编辑

I moved the edit to the Answers section and accepted it as suggested.

【问题讨论】:

  • 如果你能把那个 edit 变成一个答案并接受它,那就太好了。
  • 在为 UTF-8 配置的终端中打印 '\xe9'打印 é。它将打印一个替换字符(通常是一个问号),因为 \xe9 不是一个有效的 UTF-8 序列(它缺少两个应该跟在前导字节后面的字节)。它肯定会不会被解释为 Latin-1。
  • @MartijnPieters 我怀疑当我输出\xe9 以打印é 时,我指定终端设置为在ISO-8859-1 (latin1) 中解码的部分可能已经略过了。
  • 啊,是的,我确实错过了那部分;终端的配置与外壳不同。检查。
  • 我浏览了答案,但实际上,我有没有 u 前缀的字符串用于 python 2.7。为什么那个仍然作为unicode处理? (我的 sys.getdefaultencoding() 是 ascii)

标签: python unicode encoding ascii python-2.x


【解决方案1】:

根据Python default/implicit string encodings and conversions

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

【讨论】:

    【解决方案2】:

    当 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 警告,如果字体支持,则会显示正确的字符。即使字体 支持它,字符仍然可以从终端剪切并粘贴到具有支持字体的应用程序中,并且它是正确的。升级!

    【讨论】:

      【解决方案3】:

      它对我有用:

      import sys
      stdin, stdout = sys.stdin, sys.stdout
      reload(sys)
      sys.stdin, sys.stdout = stdin, stdout
      sys.setdefaultencoding('utf-8')
      

      【讨论】:

      • 廉价肮脏的黑客,将不可避免地破坏其他东西。正确的方法并不难!
      【解决方案4】:

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

      通过尝试打印 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(就像您更改网络浏览器的编码设置一样)。无需停止 Python 或重新启动 shell。终端的编码现在匹配 Python 的。让我们再次尝试打印:

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

      (4) python 按原样输出 binary 字符串。终端尝试使用 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 环境中获取该设置。 - 终端根据自己的编码设置显示输出。 - 终端的编码独立于 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 码点的 UTF-8 编码在 ascii 范围 (0-127) 中:

      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 码位(非 ascii)的 UTF-8 编码:

      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 指向 ©。

      【讨论】:

      • 很好的解释。现在我理解了 UTF-8!
      • 好的,我在大约 10 秒内阅读了您的整个帖子。它说,“Python 在编码方面很糟糕。”
      • 很好的解释。你能解决this的问题吗?
      【解决方案5】:

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

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

      \xe9 的情况下,Python 会采用您的默认编码 (Ascii),因此打印...一些空白。

      【讨论】:

      • 所以如果我理解得很好,当我打印出 unicode 字符串(代码点)时,python 假设我想要一个以 utf-8 编码的输出,而不是仅仅试图给我它的内容 可以使用ASCII吗?
      • @mike: AFAIK 你说的是对的。如果它确实打印出 Unicode 字符但编码为 ASCII,那么一切都会出现乱码,可能所有初学者都会问:“我怎么不能打印出 Unicode 文本?”
      • 谢谢。我实际上是那些初学者之一,但来自对 unicode 有一定了解的人,这就是为什么这种行为让我有点失望。
      • R.,不正确,因为 '\xe9' 不在 ascii 字符集中。非 Unicode 字符串使用 sys.stdout.encoding 打印,Unicode 字符串在打印前编码为 sys.stdout.encoding。
      【解决方案6】:

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

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

      【讨论】:

      • 只是出于好奇,如何将 sys.stdout.encoding 更改为 ascii?
      • @TankorSmash 我在 2.7.2 收到 TypeError: readonly attribute
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-22
      • 1970-01-01
      相关资源
      最近更新 更多