背景
本文在Window7 & python2.7.13下运行测试。
Python2处理中文字符时经常遇到乱码问题,根源在于python存储汉字的两种表示形式和Window系统编码之间的矛盾。本文通过实验,力争弄清几者的关系。首先说理论基础。
理论基础
一、Python中文字符有两种表示形式:一种是如a='巩庆奎',另一种是b=u'巩庆奎'。前者表示的是字符的编码字节序列,是Str类型,值根据采用的编码(utf、gbk)不同而不同,如GBK编码字节序列为'\xb9\xae\xc7\xec\xbf\xfc',UTF8编码字节序列为'\xe5\xb7\xa9\xe5\xba\x86\xe5\xa5\x8e'。后者是表示汉字字符在unicode表中的位置,是unicode类型,值是固定的,本例为u'\u5de9\u5e86\u594e'。
二、Window系统编码。
1. CMD命令提示符界面编码,这是CMD界面汉字的编码,CMD输入chcp显示当前界面的默认编码,本例是936/GBK,我们在CMD下输入汉字,其实是输入汉字的GBK编码后的字节序列。
2. 文本保存的编码类型。当我们新建文本文档另存为的时候,编码文件格式栏目就是该文件保存的编码格式。默认是ANSI,可以视为GBK。另外还有UTF-8和Unicode等形式。当我们读写外部文件是,这些外部文件也存在编码类型区分。
三、Python程序头部声明的编码类型。即# -*- coding:utf-8 -*- 部分。这个表示程序中常量的编码类型,需要和程序的文本保存的编码类型一致。
四、Python中type函数返回变量类型,len返回变量长度。另外print函数输出时,为了优化显示,程序后台用sys.stdout.encoding对字符进行了encoding成字节流,交给终端显示。
根据以上理论,设计实验如下:
实验
命令提示符编程
首先查看CMD界面编码,cmd界面输入chcp可见界面编码为936/GBK,编程如下:
>>> a='巩庆奎'
>>> a,type(a),len(a)
('\xb9\xae\xc7\xec\xbf\xfc', <type 'str'>, 6)
若需要存入ANSI格式文件则直接写入,读取时直接读出为GBk格式字节序列:
>>> with open('ansi.txt','w')as f:
...      f.write(a)
...
>>> with open('ansi.txt','r')as f:
...     a2=f.read()
...
>>> a2
'\xb9\xae\xc7\xec\xbf\xfc'
若存入及读取UTF8格式文件则需要:
>>> with open('utf.txt','w')as f:
...     f.write(a.decode('gbk').encode('utf'))
...
>>> with open('utf.txt','r')as f:
...     a2 = f.read()
...
>>> a2
'\xe5\xb7\xa9\xe5\xba\x86\xe5\xa5\x8e'
结果为UTF编码字符串。可见a='巩庆奎',存储的是根据GBK编码的字节序列,类型为Str,长度为6,存储文本时需要根据目标格式进行编码成Str类型。那为什么在UTF格式存储时需要decode(‘gbk’)呢,这就是下一个关键点:Unicode字符。实验
>>> b=u'巩庆奎'
>>> b
u'\u5de9\u5e86\u594e'
>>> b,type(b),len(b)
(u'\u5de9\u5e86\u594e', <type 'unicode'>, 3)
若需要存入ANSI格式文件以及读取:
>>> with open('ansi.txt','w')as f:
...     f.write(b.encode('gbk'))
...
>>> with open('ansi.txt','r')as f:
...     b2=f.read()
...
>>> b2
'\xb9\xae\xc7\xec\xbf\xfc'
>>> b2.decode('gbk')
u'\u5de9\u5e86\u594e'
若存入及读取UTF8格式文件则需要:
>>> with open('utf.txt','w')as f:
...     f.write(b.encode('utf'))
...
>>> with open('utf.txt','r')as f:
...     b2=f.read()
...
>>> b2
'\xe5\xb7\xa9\xe5\xba\x86\xe5\xa5\x8e'
>>> b2.decode('utf')
u'\u5de9\u5e86\u594e'
Unicode字符串b=u’巩庆奎’,这是巩庆奎三个汉字的Unicode码码位,是Unicode类型,存储的是汉字在Unicode表中的位置,len(a)=3。Unicode是Python2的中间转换码,编码和解码都要借助这个中间媒介。看实验:
字节序列根据自身编码解码成unicode字符,我们验证:
>>> a='巩庆奎'
>>> b=u'巩庆奎'
>>> a.decode('gbk')==b
True
Unicode字符编码成字节序列,验证:
>>> b.encode('gbk')==a
True
可知,Unicode是中间码,是汉字标准位置值。这样就能解释当gbk字节序列存储到UTF8文件格式时,需要首先decode(‘gbk’)的问题啦,这是先转成unicode,然后再encode成UTF8编码。
Python文件编程
1. 首先保证文本存储编码和文件头部声明编码一致。
2. 假设不声明而不另存为修改格式的话,则为ANSI格式文件视为GBK编码,这种情况类似命令提示符编码为GBK下编程。
3. 若声明为UTF-8类型文件,且文件另存为UTF-8类型,则文件中常量是UTF-8编码的。
a='巩庆奎'
其实a是巩庆奎这三个汉字的UTF8编码后的str类型。存储的是UTF8编码值,type(a)为str类型,长度len(a)为6。若需要存入ANSI格式文件需要decode(‘utf-8’).encode(‘gbk’),若存入UTF8格式文件则直接写入。
b=u'巩庆奎'
这是巩庆奎三个汉字的Unicode码码位,是Unicode类型,存储的是汉字在Unicode表中的位置,len(a)=3。若需要存入ANSI格式文件则f.write(a.encode(‘gbk’)),若存入UTF8格式文件则需要f.write(a.encode(‘utf-8’))。根据以上实验,总结表如下
实验结果
结论
1. Python中处理汉字有两种形式,一种是根据汉字编码方案具体编码的字节序列Str类型,另一种是根据汉字在Unicode表中固定位置的Unicode类型。(特指2.*版本,3.0以上版本换用另外的处理方式)
2. Window系统下命令提示符界面、文本文件各有编码格式。
3. Str类型是二进制编码的汉字,比较复杂多变,若需要可以根据自身编码decode成Unicode码位。Unicode类型是汉字的Unicode固定值,可以作为encode和decode的中间媒介,Unicode是码位,不是编码,需要encode成另外的编码来存储显示,unicode也只支持encode方法。Str变unicode为decode,unicode变str为encode。
4. 读写文件需要str类型,故若字串不是str,需根据目标文件格式encode。若编程文件格式与目标文件格式不一致,还需要先将其根据编程文件格式decode成unicode然后再encode成目标文件格式。
5. 界面显示需要unicode类型,故若字串不是unicode,需根据目标平台编码来decode。
6.Unicode存储为文件时,只需要根据目标文件编码encode成字节序列即可。读入文件取unicode值时,只需要根据目标文件编码decode取得的字节序列即可。