文章目录
- 解释作用
- 举例
- 编码信息的必要性
- 其他语言
- 最佳实践
- 再遇乱码
python中编码问题很复杂,我曾尝试写了一篇有关于此的文章,但是总觉得有些问题的细节没有完全说清。所以决定重新把复杂问题分解为一个个简单的小问题,挨个攻破。这是其中的第一篇。
写过Python程序的人都知道,如果想让程序能正确的处理非ASCII字符,在源码的第一行或第二行需要加这么一句:
# -*- coding: utf-8 -*-
其中的utf-8
可以换成其他编码。
这篇文章就来说说这句究竟有什么作用。
解释作用
我们知道计算机里的文件,不管是什么类型的,其本质都是保存在计算机里的二进制数据。python源代码文件也不例外。
当我们执行以下命令时,看发生了什么:
python 源码文件.py
- python解释器程序会把源码文件的二进制数据读入内存;
- python解释器会把读入内存的二进制数据翻译为python代码;
- 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
命令运行程序了,然后就会发生之前列的那三个步骤:
- python解释器程序会把源码文件的二进制数据读入内存;
- python解释器会把读入内存的二进制数据翻译为python代码;
- python解释器会执行由源码文件二进制内容翻译得到的python代码。
我们只看第2步,下面是python解释器读取并加载到内存的t.py文件的二进制内容:
23202d2a2d20636f64696e673a207574662d38202d2a2d0a733d27e4ba8c
e4b889270a7072696e742873290a
那么问题来了,怎么把这些内容翻译为python语句呢?
这个翻译过程叫做解码(decode),其正好和我们在保存源码文件的时候做的事情相反,那时候我们是选择用UTF-8对python语句进行编码,然后将编码得到二进制数据保存到文件。
现在我们需要按照一定的编码方式,将这些二进制数据解码为python语句。
那按照什么编码方式解码呢?我们知道应该按照当初保存文件时,所指定的一样的编码方式,也就是UTF-8。所以我们需要告诉python解释器,要用UTF-8进行解码,我们使用# -*- coding: utf-8 -*-
这句的目的就是告诉python解释器这个信息。
python解释器会先读取前两行文件内容(读到二进制的换行符号(0x0A
或0x0D0A
或单独的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在系统默认的命令提示符里执行程序,会看到如下打印输出:
浜屼笁
又乱码了!
这是怎么回事?下一篇我们再说。
- python2默认编码方式为ASCII,python3为UTF-8,可以用代码查看:
import sys; sys.getdefaultencoding()
。 ↩︎ - 若找到的编码方式无法识别或无法对源码内容进行解码,会编译报错。 ↩︎