Python默认字符串编码

5

Python何时、何地以及如何隐式地应用编码到字符串或进行隐式转码(转换)?

那些“默认”的(即暗示的)编码是什么?

例如,这些编码是什么:

  • of string literals?

      s = "Byte string with national characters"
      us = u"Unicode string with national characters"
    
  • of byte strings when type-converted to and from Unicode?

      data = unicode(random_byte_string)
    
  • when byte- and Unicode strings are written to/from a file or a terminal?

      print(open("The full text of War and Peace.txt").read())
    

1
你真的需要了解Python 2.x和3.x吗?因为答案非常不同,对于3.x来说更简单,对于3.7+甚至更简单。 - abarnert
1
这是关于该主题的规范答案 - 是的,我确实这么认为。 - ivan_pozdeev
说到3.7+,我对此一无所知,所以您可以添加差异。 - ivan_pozdeev
难道为2.x和3.x分别提供规范答案(在顶部相互链接)不是更好吗?这样,那些在使用3.x时没有进行移植或编写跨版本代码的人就不需要费力地浏览一大堆对他们来说令人困惑和无关紧要的内容,只需找到与他们代码相关的部分即可。 - abarnert
这个最好在元主题中讨论。到目前为止,我收到的都是对帖子的支持。 - ivan_pozdeev
2个回答

9
这里涉及到 Python 功能的多个部分:读取源代码和解析字符串文字、转码和打印。每个部分都有自己的惯例。
简短回答:
  • 用于代码解析的目的:
    • str (Py2) -- 不适用,从文件中获取原始字节
    • unicode (Py2)/str (Py3) -- "源编码",默认为 ascii (Py2) 和 utf-8 (Py3)
    • bytes (Py3) -- 无,字面值中禁止使用非ASCII字符
  • 用于转码的目的:
    • both (Py2) -- sys.getdefaultencoding()(几乎总是ascii
      • 通常会发生隐式转换,导致UnicodeDecodeError/UnicodeEncodeError
    • both (Py3) -- 无,必须在转换时明确指定编码
  • 用于I/O的目的:
    • unicode (Py2) -- 如果设置了,则为<file>.encoding,否则为sys.getdefaultencoding()
    • str (Py2) -- 不适用,写入原始字节
    • str (Py3) -- <file>.encoding,始终设置并默认为locale.getpreferredencoding()
    • bytes (Py3) -- 无,print会产生其repr()

首先,为了让您正确理解后面的内容,我们需要澄清一些术语。 解码 是将 字节 转换成 字符(Unicode 或其他字符集) 的过程,而 编码(作为一个过程)则是相反的过程。请参阅The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!) – Joel on Software以获取两者之间的区别。
现在...

读取源代码和解析字符串字面量

在源文件开头,您可以指定文件的“源编码”(其确切影响稍后会描述)。如果未指定,默认值为 Python 2 的 ascii 和 Python 3 的 utf-8。UTF-8 BOM 具有与 utf-8 编码声明相同的效果。

Python 2

Python 2以原始字节形式读取源代码。只有在遇到Unicode文字时,它才使用“源编码”来解析它们。(在底层实现上比这更复杂,但这是净效应.)

> type t.py
# Encoding: cp1251
s = "абвгд"
us = u"абвгд"
print repr(s), repr(us)
> py -2 t.py
'\xe0\xe1\xe2\xe3\xe4' u'\u0430\u0431\u0432\u0433\u0434'

<change encoding declaration in the file to cp866, do not change the contents>
> py -2 t.py
'\xe0\xe1\xe2\xe3\xe4' u'\u0440\u0441\u0442\u0443\u0444'

<transcode the file to utf-8, update declaration or replace with BOM>
> py -2 t.py
'\xd0\xb0\xd0\xb1\xd0\xb2\xd0\xb3\xd0\xb4' u'\u0430\u0431\u0432\u0433\u0434'

因此,普通字符串将包含文件中的确切字节。Unicode字符串将包含使用“源编码”解码文件字节的结果。 如果解码失败,则会出现SyntaxError。如果文件中有非ASCII字符且未指定编码,则也会出现此情况。最后,如果使用unicode_literals future,则在解析时任何常规字符串文字(仅在该文件中)都被视为Unicode文字,具有所有这意味着什么的内容。

Python 3

Python 3使用“源编码”将整个源文件解码为一系列Unicode字符。然后进行任何解析。(特别是,这使得标识符中可以使用Unicode。)由于所有字符串文字现在都是Unicode,因此不需要进行其他转换。在字节文字中,禁止使用非ASCII字符(这些字节必须用转义序列指定),从而避免了这个问题。

转码

根据开头的澄清:
  • str (Python 2)/bytes (Python 3) -- 字节 => 只能直接进行decode(详细信息见下)
  • unicode (Python 2)/str (Python 3) -- 字符 => 只能进行encode

Python 2

在这两种情况下,如果未指定编码,则使用sys.getdefaultencoding()。它是ascii(除非您取消注释site.py中的代码块,或执行其他黑客行为,这将导致灾难。因此,为了进行转码,sys.getdefaultencoding()是“字符串的默认编码”。 现在,这里有一个警告:
  • 当将 str<->unicode 进行转换时,默认情况下会隐式执行 decode()encode() 操作:

    • 在字符串格式化中(Stack Overflow 上三分之一的 UnicodeDecodeError/UnicodeEncodeError 问题都是关于此的)
    • 尝试对 str 进行 encode() 或对 unicode 进行 decode()(Stack Overflow 上三分之二的问题都是关于此的)

Python 3

现在根本没有“默认编码”:strbytes 之间的隐式转换已被禁止。

  • bytes 只能被 decodestr -- encode,且必须指定 encoding 参数。
  • bytes->str(包括隐式转换)转换会产生其 repr()(仅用于调试打印),完全规避了编码问题。
  • 禁止将 str->bytes 转换。

打印

这个问题 与变量的值无关,但与在屏幕上 print 时看到的内容以及是否在 print 时出现 UnicodeEncodeError 相关。

Python 2

  • 如果设置了<file>.encoding,则unicode将使用该编码进行encode;否则,根据上述情况隐式转换为str。(最后三分之一的UnicodeEncodeError SO问题属于这里。)
    • 对于标准流,流的编码在启动时从各种环境特定源猜测,并可以通过PYTHONIOENCODING环境变量进行覆盖。
  • str的字节会按原样发送到操作系统流中。您在屏幕上看到的具体字形取决于终端的编码设置(如果是类似UTF-8的东西,则如果打印无效的UTF-8字节序列,则可能什么也看不到)。

Python 3

更改如下:

  • 现在以文本或二进制mode打开的file原生地接受相应的strbytes,并且会直接拒绝处理错误的类型。文本模式文件始终有一个encoding设置,其中locale.getpreferredencoding(False)是默认值
  • 对于文本流的print仍然会隐式地将所有内容转换为str,在处理bytes时会像上面一样打印其repr(),从而完全避免了编码问题。

Python 3 在索引字节字符串时使用什么编码?例如,x = b'j'; x[0]; 输出106(j的ascii表示)。这不是使用ascii作为默认解码方法吗? - WalksB
1
在字节字符串文字中,只允许使用ASCII字符和显式代码(\xnn),请注意@ZaidGharaybeh。 - ivan_pozdeev

1
作为存储字符串/数组的内部格式,隐式编码:您不必担心编码。实际上,Python以一种Python内部方式解码字符。它大多是透明的。只需想象它是一个Unicode文本或字节序列,以抽象方式呈现。

Python 3.x中的内部编码根据“较大”的字符而异。它可以是UTF-8/ ASCII(用于ASCII字符串),UTF-16UTF-32。当您使用字符串时,就像您拥有一个Unicode字符串(因此是抽象的,而不是真正的编码)。如果您不使用C编程或使用某些特殊函数(内存视图),则永远无法看到内部编码。

字节只是实际内存的视图。 Python将其解释为unsigned char。但同样,通常您应该考虑序列是什么,而不是内部编码。

Python 2有字节和字符串作为无符号字符,Unicode作为UCS-2(因此在Python 2中,代码点超过65535的将使用两个字符(UCS-2)编码,在Python 3中只使用一个字符(UTF-32)编码)。


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