Python2中文乱码处理

背景

本文在Window7 & python2.7.13下运行测试。

Python2处理中文字符时经常遇到乱码问题,根源在于python存储汉字的两种表示形式和Window系统编码之间的矛盾。本文通过实验,力争弄清几者的关系。首先说理论基础。

理论基础

一、Python中文字符有两种表示形式:一种是如a='巩庆奎',另一种是b=u'巩庆奎'。前者表示的是字符的编码字节序列,是Str类型,值根据采用的编码(utfgbk)不同而不同,如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-8Unicode等形式。当我们读写外部文件是,这些外部文件也存在编码类型区分。

三、Python程序头部声明的编码类型。即# -*- coding:utf-8 -*- 部分。这个表示程序中常量的编码类型,需要和程序的文本保存的编码类型一致。

四、Pythontype函数返回变量类型,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)=3Unicode是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’))。根据以上实验,总结表如下

实验结果

Python2中文乱码处理Python2中文乱码处理

结论

1. Python中处理汉字有两种形式,一种是根据汉字编码方案具体编码的字节序列Str类型,另一种是根据汉字在Unicode表中固定位置的Unicode类型。(特指2.*版本,3.0以上版本换用另外的处理方式)

2. Window系统下命令提示符界面、文本文件各有编码格式。

3. Str类型是二进制编码的汉字,比较复杂多变,若需要可以根据自身编码decodeUnicode码位。Unicode类型是汉字的Unicode固定值,可以作为encode和decode的中间媒介,Unicode是码位,不是编码,需要encode成另外的编码来存储显示,unicode也只支持encode方法。Strunicodedecodeunicodestrencode

4. 读写文件需要str类型,故若字串不是str,需根据目标文件格式encode。若编程文件格式与目标文件格式不一致,还需要先将其根据编程文件格式decodeunicode然后再encode成目标文件格式。

5. 界面显示需要unicode类型,故若字串不是unicode,需根据目标平台编码来decode

6.Unicode存储为文件时,只需要根据目标文件编码encode成字节序列即可。读入文件取unicode值时,只需要根据目标文件编码decode取得的字节序列即可。