写python的过程中经常出现各种蛋疼的编码问题,于是通过上网查资料,自己做实验,想彻底搞清楚这个问题。
编码和解码的理解
计算机是不认识字符的,计算机只认识二进制的01串,那么字符要存储在计算机中,首先要做的就是把字符用二进制的01串来表示,这就是所谓的编码(encode)
;当我们要阅读存储在计算机中的字符时,计算机就需要把二进制的01串转换成我们可以读的字符,这就是解码(decode)
;所以,我们遇到encode error
, 一般是计算机需要把某个字符进行存储时,使用的编码找不到该字符对应的01串,而我们遇到decode error
时,一般是计算机读取以01串存储的数据,准备转化成我们可识别的字符时,使用的编码识别不了01串。不论是从字符转化成二进制的01串进行存储,还是从二进制的01串转化成我们可读的字符,都需要一个对应的转化表,也就是哪个字符对应哪个01串,关于这样的对应关系有很多种(比如utf-8, gbk等等),就称为编码方式(简称编码,名词)。显然,每种编码方式可以编码的字符都是有限的,那么一种编码方式可以编码的字符集就称作字符集吧。
如果我们用一种编码方式 A 进行encode,用另一种编码方式B进行decode,以“ 我 ” 这个汉字为例,那么就会出现三种情况:一,A和B所使用的字符集中都可以找到“ 我 ” 这个字符,而且A和B表示“ 我 ” 这个字的01串也一样,所以“ 我 ” 这个字的编码和解码就不会存在问题;二,A和B所使用的字符集中都可以找到“ 我 ” 这个字符,但是A和B表示“ 我 ” 这个字符的01串不一致,这时候A按照自己的编码方式将" 我 “ 存储到计算机中,B按照自己的编码方式解读A对“ 我 ” 这个字表示的01串时,就会出现两种情况,一是可以解释,但是显然解释是错误的,可能会对应到另外的字符,这就称之为乱码,我们会看到一堆无意义的字符,另一种是直接不能解释,这时候程序会直接报错,python中就是decode error
; 第三种是B所使用的编码集种直接无法找到“ 我 ”, 其表现和第二种一样。
python中的编码
一、python文件的编码
python文件是由python语言解释器进行解释执行的,默认情况下python解释器对python文件用ascii编码方式进行解码,因此如果python文件中包含中文字符,就会报错,如下面test.py的代码
# main 程序
def main():
print('hello, world!')
main()
执行python test.py
会得到
File "test.py", line 1
SyntaxError: Non-ASCII character '\xe7' in file test.py on line 1, but no
encoding declared; see http://python.org/dev/peps/pep-0263/ for details
我们可以看到Non-ASCII character '\xe5' in file
,也就是说没有声明编码的情况下,python解释器按照ascii解码的时候不认识'\xe5',现在在这个文件的头部加入编码声明,那么声明为什么编码呢?python解释器读的是这个文件,因此这个文件是按照什么编码存储的就按照什么编码声明,我的编辑器是utf-8编码的,因此我声明为utf-8, 代码如下
# coding: utf-8
# main 程序
def main():
print('hello, world')
main()
这次再执行这个文件,就正常输出了 “hello, world", 刚才这个编码问题就是我们的编辑器保存代码使用的编码和python解释器解释代码使用的编码不一致导致的,因此我们通过 # coding: utf-8
告诉python解释器应该使用的编码,这个问题就解决了。
二、python中的字符串和unicode
由于我使用的是python2.7, 因此,仅针对python2.7讨论这个问题。python中有str和unicode两种表示字符串的方式,他们均继承自basestring, 但是却是完全不同的两个东东。str可以说并不是真正的字符串,而是已经经过编码的二进制01串,而unicode确实真正的字符串。
# coding: utf-8
# main 程序
def main():
u=u'大家好'
s='大家好'
print(len(u))
print(len(s))
main()
这段代码的输出是3和9,3我们很好理解,本来就是3个汉字;为什么s的长度是9呢?就是因为s是'大家好'这三个汉字已经编码得到的长度为9字节的01串。这三个汉字已经编码了,那么使用什么编码方式呢?就是编辑器所使用的编码方式,在我这儿也就是utf-8, 我们再做一个实验,对这个问题有更加深刻的理解。
# coding: utf-8
# main 程序
def main():
u=u'大家好'
print(u)
main()
我执行 python test.py
,正常输出 “ 大家好 ", 然后我想让这个输出保存到文件中,因此我执行 python test.py > out.txt
, 于是问题出现了,输出下面的错误
Traceback (most recent call last):
File "test.py", line 8, in <module>
main()
File "test.py", line 6, in main
print(u)
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-2:
ordinal not in range(128)
为什么我直接输出到终端的时候一切正常,当我想重定向到文件的时候出问题了呢?当我直接输出至终端的时候,按照终端所使用的编码来编码‘大家好’这3个字符,而我的终端所使用的编码是utf-8,和我的编辑器所使用的编码一致,所以不存在问题。但是当我把这个输出要重定向至文件的时候,就出问题了,因为文件本身并不规定编码方式,我也没有对这个字符串进行显式编码,因此python将采用默认的编码方式,而python2.7的默认编码是ascii
,因此会出现UnicodeEncodeError
,搞清楚问题以后,也就有办法解决问题了,一种是我们对这个unicode字符串进行显式编码, 如下所示
# coding: utf-8
# main 程序
def main():
u=u'大家好'
print(u.encode('utf-8'))
main()
另一种是改变python的默认编码为我们这个字符串的编码, 如下所示
# coding: utf-8
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
# main 程序
def main():
u=u'大家好'
print(u)
main()
一般推荐第一种方式,尽量避免第二种方式,因为第二种方式只有当我们需要处理的编码方式只有一种时有效,如果我们使用多种编码方式,那么仍然会存在问题。我们再看一段代码
# coding: utf-8
# main 程序
def main():
s='大家好'
print(s)
main()
对这段代码执行python test.py
直接输出在终端和python test.py > out.txt
都可以得到预期效果,这又是为什么呢?这段代码和之前代码的区别是'大家好'这个字符串是str而不是unicode类型。我们知道str类型是已经编码的01串,因此在输出至终端或文件时不需要再进行编码,所以不会出现之前遇到的问题。
三、思考
这样看,貌似使用str类型更方便一些,省去了我们进行显式的编码了,实则恰恰相反,因为这种隐式的编码方式很不利于代码维护,虽然代码暂时很侥幸,很容易的运行通过了,但是日后我们很难搞清楚这个str里面究竟是什么编码,而且我们也看到通过len拿到的字符串长度也存在问题。而使用第一种unicode的方式,虽然我们写代码时需要多费些功夫,在输出时需要显式进行编码,但是这样也明确了这个字符串所采用的编码,同时拿到的字符串长度也是准确的。