GBK or UTF-8? Windows 平台 Python3 读写文件

2017-11-08
  1. Python
  2. UTF-8
  3. GBK
  4. Windows
  5. 文件

想不到,我在简书正式写的第一篇文章,居然是关于代码的。

问题

昨晚,在 Windows 10 平台上编写一个 Python3 程序时,需要打开一个文件并从中读取一定内容。

按照我之前的做法,无非是:

with open("xx.txt","r") as file:
  data=file.read()

然而,这一次,问题出现了:

文件中含有的一个字符「½」,并不能正常显示,在 Python3 中用 print() 打印出来,显示为:「陆」。

显然,出现了编码问题。

何以如此?

首先,由于 Windows 平台的默认编码是 GBK,而所打开的文件编码是 UTF-8,很有可能是混淆了两者编码方式,从而出错。那么,我查询了「陆」和「½」这两个字符的多种编码方式,如下:

字符 GBK UTF-8 Unicode
C2BD E99986 9646
½ C2BD BD

可以发现,「½」的 UTF-8 编码和「陆」的 GBK 编码结果一致。

这也就解释了为什么「½」会变成「陆」:文件中「½」的 UTF-8 编码被错误地当作 GBK 编码来解释,从而产生了「陆」。

然而,为什么会这样呢?为什么文件中的其他字符并不会产生这样的错误呢?

以字符「K」、「7」、「。」、「辄」为例(分别代表英文字符、数字、中文符号、中文字符)比较它们的编码:

字符 GBK UTF-8 Unicode
K 4B 4B 4B
7 37 37 37
A1A3 E38082 3002
E9FC E8BE84 8F84

可以看出,对于原生的 ASCII 字符,其 GBK 和 UTF-8 编码完全一致,所以不会产生任何问题,即便混淆了编码方式;但是对于非 ASCII 字符,如中文字符,其 GBK 编码占用 2 个字节,UTF-8 编码占用 3 个字节,且两者完全不一致,故混淆编码方式会出现错误。

比如,倘若文件内容是「无辄」,按照本文开头的方式打开此文件,会变成「鏃犺緞」;倘若文件内容是「辄」,则会抛出 UnicodeDecodeError 错误。

原因很简单,「无辄」的 UTF-8 编码结果有 6 个字节,被错误地解释为 3 个 GBK 编码的字符;而「辄」仅 3 个字节,无法按照 GBK 编码理解,因此直接报错。

而「½」更为特殊的是,不同于一般的中文字符,它的 UTF-8 编码是 2 个字节,因此被错误地用 GBK 打开时并不会报错。

如何解决?

不妨先查阅一下 Python3 中关于 open() 函数的说明,在 Python 交互界面中输入:

help(open)

结果如下(已省略无关内容):

Help on built-in function open in module io:

open(file, mode=‘r’, buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)

encoding is the name of the encoding used to decode or encode the file. This should only be used in text mode. The default encoding is platform dependent, but any encoding supported by Python can be passed. See the codecs module for the list of supported encodings.

注意加粗部分,在不指定 encoding 参数时,open() 函数默认采用和系统相关的编码方式。而在 Windows 系统下,系统相关的编码方式是 GBK,不是 UTF-8。

这便是产生错误的根源。

那么,解决方法也很简单,只需要指定 encoding="utf8" 即可。

with open("xx.txt","r",encoding="utf8") as file:
  data=file.read()

从此天下太平。

反思

虽然 Python3 中的 str 采用 Unicode 编码方式,相比于 Python2 对于中文的支持有了很大的提升,但为了尽可能地减少麻烦,在涉及到中文或其他非 ASCII 字符时,务必记得设定编码方式为 UTF-8。

尤其是在 Windows 平台。