Python中的编码

小秦在日常写代码的时候,python中的中文一直会有乱码的情况出现。今天小秦就研究下这个事情,看看是什么原因造成的。
先来大概说一下这方面要注意的地方:
1.2.X和3.X有一些不同
2.python中str和unicode是两种不同的字符集
3.内存中的字符表示和磁盘中的有些不同
4.str不知道字符的格式,需要从外界感知

参考链接:
http://hi.baidu.com/ji_haiyang/item/2729881e3e0537011894ecc1
http://www.cnblogs.com/huxi/archive/2010/12/05/1897271.html

比如下面这行代码:

print "中文"

在实际运行的时候会报下面的错误:

SyntaxError: Non-ASCII character '\xe4' in file C:\Users\qinth\Desktop\TEMP\python_study\test.py on line 2, but no encoding declared; see http://www.python.org/peps/pep-0263.html for details

这个是什么原因呢?首先要搞懂的是,python默认支持的编码是ASCII的,而中文之类的语言无法用ASCII来表示,这类语言在python中使用unicode来存储(虽然也可以用gb2312之类的编码格式,但是在python中用的是unicode)。ASCII在python中的字符类型是str,而unicode则是unicode。在python中默认的unicode的表示方法是UTF-8(表示方法就是磁盘上的raw bytes的存储格式。比如我uncide的中文“好”的编码是1234,这个1234存储在磁盘上可以有很多中存储格式,比如1就是01,2就是10,3就是11,4就是00,那么磁盘上可以是01101100,但也可以是1为001,2是010,3是011,4是000,那么磁盘上可以是001010011000。UTF-8就是类似于这样的一种unicode在磁盘上的raw bytes的表示方法)。比如下面这个例子:

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

string = u"abs"
print isinstance(string,unicode)
print isinstance(string,str)

string = "abs"
print isinstance(string,unicode)
print isinstance(string,str)

它的输出是:

True
False
False
True

另外要注意的是,在2.X版本中的str在3.X中就是bytes,而2.X中的unicode在3.X中就是str。小秦的这个文章主要是以2.X为主。

那为什么str是ASCII类型呢?从3.X中的做法就能知道,其实str并不知道字符串是什么类型,它是通过sys.getdefaultencoding()来获取当前系统的默认编码格式的。在小秦的系统中是ascii。所以说unicode对于python来说只是str的一种特例,比如我把系统的默认编码改成utf-8,那么str和unicode区别就不大了。另外可以在python文件的开头通过类似# -*- coding: utf-8 -*-的代码来表明代码中出现的字符的格式,其实就是告诉python解析器我的str中的字符是什么格式。这里只有#、coding和utf-8是有用的,其它的只是美化下而已。比如这个例子,由于小秦在开头指定了格式是utf-8,所以str中的raw bytes就是utf-8的格式,虽然str默认还是按照ASCII来理解这些raw bytes的二进制代码串,但输出的时候通过解码,可以得到正确的输出:

#-*- coding: UTF-8 -*-

string = "中文"

print string.decode('utf-8')

等下,如果真的去运行这个,会发现还是会出现报错信息,这事为啥呢?原因在于print这个方法。print使用标准输出输出代码,也就是使用sys.stdout.encoding的编码。在windows下这个编码是None,于是会用默认的ascii去解码python内部的unicode表示,造成了报错。这里就比较头疼了,一个解决方法是sys.getdefaultencoding()显示不是ascii。于是代码变成下面这样:

#-*- coding: UTF-8 -*-
import sys

string = "中文"

reload(sys)
sys.setdefaultencoding('utf-8')

print string.decode('utf-8')

这个时候就能正常输出啦。这个例子还是很重要的,再来看下字符集的转换:首先通过第一行,声明我们的python字符串的编码格式都是utf-8的。然后通过sys的setdefaultencoding方法,告知系统的默认字符集是utf-8。当我们的string字符串被赋值的时候,其不知道字符串的格式,于是print的时候我们先告知下字符串格式,以免其解析错了,但print输出会的把python内存中的字符集格式再做一个转换(感觉也不是转换,就是一个直接的输出),转换成sys.stdout.encoding的编码,如果这个编码是None则使用sys.getdefaultencoding(),所以这里按照utf-8进行输出。由于utf-8可以按照utf-8输出(这是肯定的啦),所以就能输出了。

看了这个例子后,可以知道在python的字符处理中有两个比较重要的方法:decode和encode。根据上面的第二个链接,博主的说明写的非常的好:“内置的open()方法打开文件时,read()读取的是str,读取后需要使用正确的编码格式进行decode()。write()写入时,如果参数是unicode,则需要使用你希望写入的编码进行encode(),如果是其他编码格式的str,则需要先用该str的编码进行decode(),转成unicode后再使用写入的编码进行encode()。如果直接将unicode作为参数传入write()方法,Python将先使用源代码文件声明的字符编码进行编码然后写入。”

也就是说,decode就是把raw bytes转成python自己的格式(一般是UTF-16),而encode则是把python自己的格式转成指定的格式,一般也就是UTF-16到UTF-8之类的。

刚刚讲的是关于磁盘的,现在看看内存。在python中不管最终写入磁盘是安装哪种raw bytes格式写入的,在内存中始终只有一种格式。在3.2和之前的版本使用的是UTF-16。也就是说只有最终写入磁盘的时候,才会按照要求进行encode。

小秦这里可能表述的还不是很好,非常推荐看下上面给出的两个链接中的文章。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*