文章目录

  • 解释作用
  • 举例
  • 编码信息的必要性
  • 其他语言
  • 最佳实践
  • 再遇乱码


python中编码问题很复杂,我曾尝试写了一篇有关于此的文章,但是总觉得有些问题的细节没有完全说清。所以决定重新把复杂问题分解为一个个简单的小问题,挨个攻破。这是其中的第一篇。

写过Python程序的人都知道,如果想让程序能正确的处理非ASCII字符,在源码的第一行或第二行需要加这么一句:

# -*- coding: utf-8 -*-

其中的utf-8可以换成其他编码。

这篇文章就来说说这句究竟有什么作用。

解释作用

我们知道计算机里的文件,不管是什么类型的,其本质都是保存在计算机里的二进制数据。python源代码文件也不例外。

当我们执行以下命令时,看发生了什么:

python 源码文件.py
  1. python解释器程序会把源码文件的二进制数据读入内存;
  2. python解释器会把读入内存的二进制数据翻译为python代码;
  3. python解释器会执行由源码文件二进制内容翻译得到的python代码。

在这里3个步骤的第2个步骤里,就需要用到# -*- coding: utf-8 -*-这里面的编码信息,这就是它的作用。

举例

下面我们来举个具体的例子,以便更清楚这个过程的细节。

首先我们编写python代码:

# -*- coding: utf-8 -*-
s='二三'
print(s)

然后将其保存为文件。

注意: 保存文件的时候需要指定文件编码,这里使用UTF-8编码,得到文件:t.py。

如前所述,文件是以二进制的形式保存在磁盘上的,我们可以使用xxd命令查看文件的二进制内容:

$ xxd t.py
00000000: 2320 2d2a 2d20 636f 6469 6e67 3a20 7574  # -*- coding: ut
00000010: 662d 3820 2d2a 2d0a 733d 27e4 ba8c e4b8  f-8 -*-.s='.....
00000020: 8927 0a70 7269 6e74 2873 290a            .'.print(s).

保存了源码文件后,自然就可以使用python命令运行程序了,然后就会发生之前列的那三个步骤:

  1. python解释器程序会把源码文件的二进制数据读入内存;
  2. python解释器会把读入内存的二进制数据翻译为python代码;
  3. python解释器会执行由源码文件二进制内容翻译得到的python代码。

我们只看第2步,下面是python解释器读取并加载到内存的t.py文件的二进制内容:

23202d2a2d20636f64696e673a207574662d38202d2a2d0a733d27e4ba8c
e4b889270a7072696e742873290a

那么问题来了,怎么把这些内容翻译为python语句呢?

这个翻译过程叫做解码(decode),其正好和我们在保存源码文件的时候做的事情相反,那时候我们是选择用UTF-8对python语句进行编码,然后将编码得到二进制数据保存到文件。

现在我们需要按照一定的编码方式,将这些二进制数据解码为python语句。

那按照什么编码方式解码呢?我们知道应该按照当初保存文件时,所指定的一样的编码方式,也就是UTF-8。所以我们需要告诉python解释器,要用UTF-8进行解码,我们使用# -*- coding: utf-8 -*-这句的目的就是告诉python解释器这个信息。

python解释器会先读取前两行文件内容(读到二进制的换行符号(0x0A0x0D0A单独的0x0D)就是一行),然后用默认的编码方式1对其进行解码,之后会用正则表达式coding[:=]\s*([-\w.]+)进行匹配查找,用找到的正则表达式的第一个分组作为编码方式2,对源码文件的二进制内容进行解码,得到python语句。

注意: 在编码信息所在行(也就是coding[:=]\s*([-\w.]+)所在行)和之前行不能有python语句(可以有注释)。

编码信息的必要性

解释器为什么要获取编码信息呢,如果使用不同编码方式进行解码会怎么样?

答案是:会得到不同的python语句内容。

如:若使用GBK对上面的二进制数据进行解码,会得到这样的python语句内容:

# -*- coding: utf-8 -*-
s='浜屼笁'
print(s)

面对不同的代码内容,其执行结果不同就很容易理解啦。

其他语言

其实不光python语言,其他语言也会面对同样的问题。如Java在对源码进行编译的时候,默认会采用操作系统的编码方式解码源文件,若源文件编码和系统编码不一致时,就可能乱码。

这时候就需要手动指定编码方式了:

javac -encoding utf-8 Test.java

最佳实践

知道了作用原理就很容易给出最佳实践了,那就是:

python源代码文件在保存时,使用的文件编码,一定要和源码中# -*- coding: xxx -*-所给出的编码一样。

我个人推荐使用UTF-8编码。

再遇乱码

下面我们遵循最佳实践,保证t.py的文件编码为UTF-8,同时保证其内容为:

# -*- coding: utf-8 -*-
s='二三'
print(s)

然后运行程序。

在Linux下执行程序都没问题。

在默认编码为中文的Windows下用python3执行程序也没有问题。

直到我们在默认编码为中文的Windows下,用python2在系统默认的命令提示符里执行程序,会看到如下打印输出:

浜屼笁

又乱码了!

这是怎么回事?下一篇我们再说。


  1. python2默认编码方式为ASCII,python3为UTF-8,可以用代码查看:import sys; sys.getdefaultencoding()。 ↩︎
  2. 若找到的编码方式无法识别或无法对源码内容进行解码,会编译报错。 ↩︎