字符编码问题是每个程序员必定会遇到的,同样,python的中文问题一直是一个非常令人头疼的问题,本文将介绍Python中涉及到中文细节问题。
建议在阅读这篇文章之前,先前往字符编码详解这篇文章了解相关字符编码的原理细节。
在Python中有两种默认的字符串:str和Unicode:
在Windows控制台测试str字符串如下,可以看到str字符串的类型为"str",使用len得到的长度为4,表示这个字节流一共有4个字节。通过前面字符编码详解这篇文章,应该可以知道“你好”这个词中“你”的GBK编码为0xC4E3,“好”则为0xBAC3,对比结果中s的输出可知,Python中默认就是使用了GBK进行编码,然后把编码后的字节流保存到了str数组s中。
>>> s = '你好' >>> type(s)>>> s '\xc4\xe3\xba\xc3' >>> print s 你好 >>> len(s) 4
在Windows控制台测试Unicode如下,可以看到Unicode字符串的类型为"unicode",使用len得到的长度为2,表示了这个字符串的真正长度。
而通过前面字符编码详解这篇文章,应该可以知道“你好”这个词中“你”的UTF-16大端字节序编码为0x4F60,“好”则为0x597D,对比结果中u的输出可知,Python控制台下的Unicode对象默认使用UTF-16大端字节序进行编码。
>>> u = u'你好' >>> type(u)>>> u u'\u4f60\u597d' >>> print u 你好 >>> len(u) 2
由上面可知,使用str数组时得到的长度是字符串编码后的字节的个数,而实际上我们通常关注的是字符串本身的长度。另外,使用Python的切片操作会产生同样的问题,为了减少不必要的麻烦,在实际的编程工作中,我们可以按如下方法进行:
str字符串是原字符使用特定的编码方法得到的字节流,如果知道了该编码方法,我们可以使用decode函数把str字节流解码为原先的字符串的Unicode对象。而Unicode对象也可以使用encode函数编码成为对应的字节流。
例如,根据前面我们知道了Python在控制台下str字符串默认是GBK编码后的字节流,以下我们把str字符串使用GBK解码为Unicode字符串,同样使用GBK把Unicode字符串编码为str字节流。可以看到,通过转化后得到的结果是一样的。
>>> s = '你好' >>> s '\xc4\xe3\xba\xc3' >>> s.decode('GBK') u'\u4f60\u597d' >>> u = u'你好' >>> u u'\u4f60\u597d' >>> u.encode('GBK') '\xc4\xe3\xba\xc3'
下面我们看一下把Unicode使用其他方法编码后再控制台下的显示结果。
>>> u = u'你好' >>> print u 你好 >>> print u.encode('GBK') 你好 >>> print u.encode('UTF-8') 浣犲ソ >>> print u.encode('UTF-16') O}Y
由前面可以知道,Windows下控制台的默认使用的编码是GBK,所以我们把u编码为GBK时可以正常显示,而编码为UTF-8或UTF-16时则显示乱码。而在控制台下直接输出Unicode字符串也能正常显示,这是Python在向控制台输出unicode对象的时候会自动根据输出环境的编码进行转换,而如果输出的不是unicode对象而是普通的str字符串,则会直接按照该字符串的编码输出字符串,从而出现上面的现象。
以上的测试均是在Windows控制台的Python下进行,但是在Python自带的IDE即IDLE下情况有所不同。
>>> s = '你好' >>> s '\xc4\xe3\xba\xc3' >>> u = u'你好' >>> u u'\xc4\xe3\xba\xc3' >>> print s 你好 >>> print u ??o?
IDLE中str字符串也是默认采用GBK编码得到,而Unicode对象内部采用的也是GBK编码,但是不能正常输出,而且以下试图把它转为其他编码时也不能输出。
>>> print u.encode('GBK') Traceback (most recent call last): File "", line 1, in print u.encode('GBK') UnicodeEncodeError: 'gbk' codec can't encode character u'\xc4' in position 0: illegal multibyte sequence >>> print u.encode('UTF-8') 脛茫潞脙
但把str字符串解码为Unicode对象时则和控制台下情况一致,并且可以正常显示。
>>> uu = s.decode('GBK') >>> uu u'\u4f60\u597d' >>> print uu 你好
如果是在脚本中使用Python,因为Python默认脚本文件都是ASCII编码的,当文件中有非ASCII编码范围内的字符的时候就要添加编码声明:在第一行或第二行添加
# -*- coding=UTF-8 -*- 或 #coding=UTF-8
其中编码类型可以改为GBK等其他类型。否则会出现错误:
SyntaxError: Non-ASCII character '\xc4' in file C:\Users\Erichsh\a.py on line 1, but no encoding declared; see http://www.python.org/peps/pep-0263.html for details。
如果使用IDLE,则会提示:
Non-ASCII found, yet no encoding declared. Add a line like # -*- coding: cp936 -*- to you file
文件编码指的是具体的代码文本在保存时使用的编码方法,如果声明的编码和文件实际的编码不一致的话,很可能会出现乱码甚至出错等问题。原因是对于类似于u’你好’这样声明的字符串,在源代码文件里经过了该文件指定编码方法的编码被保存成字节流,程序运行时读取到这个字节流,就会使用编码声明中的编码去解码得到原始字符串,然后对这个原始字符串再用Unicode的内部编码方案进行存储。而对于’你好’这样不带u的字符串,则表示字节流,此时会直接用文件编码后的字节数组进行表示,不需要使用编码声明中的编码方案进行解码。例如,建立如下内容的脚本文件然后保存成ANSI编码:
# -*- coding: UTF-8 -*- s = '你好' print repr(s), s u = u'你好' print repr(u), u
运行后得到错误
SyntaxError: (unicode error) ‘utf8′ codec can’t decode byte 0xc4 in position 0: invalid continuation byte
但把脚本改成:
# -*- coding: UTF-8 -*- s = '你好' print repr(s), s
则可以得到以下结果:
'\xc4\xe3\xba\xc3' 你好 #原文件使用的是ANSI编码,所以得到GBK编码,GBK编码可以正常显示
对于GBK的编码声明,代码如下:
# -*- coding: GBK -*- s = '你好' print repr(s), s u = u'你好' print repr(u), u
此时如果把源文件改成UTF-8编码,对于带BOM的情况,直接返回错误:SyntaxError: encoding problem: utf-8
而对于不带BOM的情况,则返回结果:
'\xe4\xbd\xa0\xe5\xa5\xbd' 浣犲ソ u'\u6d63\u72b2\u30bd' 浣犲ソ
原因是’你好’直接用其在文件中的编码表示了,而u’你好’会用编码声明GBK去解码其存储在文件中的UTF-8表示,结果得不到其原本的字符串,反而得到一个新的字符串’浣犲ソ’,这个新的字符串再用Unicode内部编码成为’\u6d63\u72b2\u30bd’。
不过现在的IDE如IDLE一般比较智能,会根据代码中的编码声明采用同样的编码方案保存代码文件,所以以上实验如果在IDLE中结果可能不一样。例如,
但如果是使用文本编辑器编写的代码,需要特别注意保存文件时使用的编码方法要和编码声明中一致。
另外,如果使用了print输出时,也要注意编码方案是否和Windows的控制台默认编码一致。
例如,编写一下test.py脚本并使用ANSI编码保存。
# -*- coding: GBK -*- s = '你好' print repr(s), s u = u'你好' print repr(u), u
运行时得到如下结果,其中str字符串根据编码声明采用了GBK编码,然后可以正常输出。Unicode字符串采用了默认的UTF-16编码,然后输出时也被自动转换成输出环境的编码,从而显示正常。
'\xc4\xe3\xba\xc3' 你好 u'\u4f60\u597d' 你好
但如果把第一句声明改成:# -*- coding: UTF-8 -*-,则得到如下结果。其中str字符串根据编码声明采用了UTF-8编码,然而输出时产生了乱码,这是因为Windows控制台默认编码为GBK,str字符串(字节流)输出时不需要转化直接根据GBK的规则进行显示,由于这里str字符串本身不是GBK编码,于是不能正确显示,这时可以通过print s.decode(‘UTF-8′) 或print s.decode(‘UTF-8′).encode(‘GBK’)进行显示。而Unicode情况则跟之前一样。
'\xe4\xbd\xa0\xe5\xa5\xbd' 浣犲ソ u'\u4f60\u597d' 你好
另外,通过这两个例子可以知道,编码类型对Unicode对象的编码并没有影响,它内部均是采用了UTF-16进行编码,由于它对程序员透明,我们也不需要动它。注意,这里也是使用了IDLE进行运行,但是Unicode对象的内部编码并没有跟上一小节那样使用GBK进行,不同之处是这里是脚本运行,而上一节是在交互环境下运行。
下面我们看看使用Python读取中文文件并在终端显示内容会发生什么事情。新建一个脚本并写入如下内容,
# -*- coding: UTF-8 -*- s = open("text.txt").read() print repr(s), s
然后创建一个文本文件text.txt并写入两个字”你好“,下面看保存成不同的编码格式时不同的运行结果:
可以看到,s直接就得到了文件中字符串在文本保存时的编码字节流,然后print输出时,第一个GBK编码的能正常输出,第二个UTF-8编码的被控制台用GBK解码成了别的字符串而产生乱码,第三个字节流前面有3个字节(BOM)表示使用了UTF-8编码,然后也输出正常,估计可能Python能够识别这个BOM然后使用了对应的编码格式进行解析。
可以看到,这里编码声明对结果并无影响,原因是read()函数直接把字符串在文件中的编码字节流读取进来了,而这个字节流只跟源文件的编码格式相关。
内置的open()方法打开文件时,read()读取的是str,读取后需要使用正确的编码格式进行decode()。write()写入时,如果参数是unicode,则需要使用你希望写入的编码进行encode(),如果是其他编码格式的str,则需要先用该str的编码进行decode(),转成unicode后再使用写入的编码进行encode()。如果直接将unicode作为参数传入write()方法,Python将先使用源代码文件声明的字符编码进行编码然后写入。
模块codecs提供了一个open()方法,可以指定一个编码打开文件,使用这个方法打开的文件读取返回的将是unicode。写入时,如果参数是unicode,则使用open()时指定的编码进行编码后写入;如果是str,则先根据源代码文件声明的字符编码,解码成unicode后再进行前述操作。相对内置的open()来说,这个方法比较不容易在编码上出现问题。
# -*- coding: GBK -*- import codecs f = codecs.open('text.txt', encoding='UTF-8') u = f.read() #u为Unicode对象 f.close() f = codecs.open('text.txt','a', encoding='UTF-8') u = u'你好' f.write(u)