python编码问题

96
allen哦
2017.04.16 21:36* 字数 2068

写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的方式,虽然我们写代码时需要多费些功夫,在输出时需要显式进行编码,但是这样也明确了这个字符串所采用的编码,同时拿到的字符串长度也是准确的。

技术之路
Web note ad 1