初学 Python2,感觉Python2中的编码问题一直很烦人,无意中看到几篇博文 [1] [2],感觉豁然开朗,做成笔记,方便日后查阅。
A
对应的二进制数值为01000001
,对应的十进制数就是65。GB2312
,又称GB0,共收录了6763个汉字,兼容ASCII。后来扩展到GBK
,收录了27484个汉字,同时还收录了藏文等少数名族文字。GBK也是兼容ASCII,英文字符用1个字节表示,汉字用两个字节表示。字节与字符:
编码与解码
Python2默认用ASCII编码:
import sys
print sys.getdefaultencoding() # ascii
就是说默认的解释器会把str类型的字符串当做ASCII编码来处理。
注意区分的是:文档开头往往加上一句# coding: utf-8
是来指定脚本文件的编码方式。
Python2中,str和unicode都是basestring的子类,可见str和unicode是两种不同类型的字符串对象。
以汉字“禅”为例,str打印出来就是十六进制形式的\xe7\xa6\x85,对应于一长串二进制序列;而用unicode打印出来就是unicode符号u'\u7985':
>>> s = '禅'
>>> s
'\xe7\xa6\x85'
>>> type(s)
<type 'str'>
>>> u = u'禅'
>>> u
u'\u7985'
>>> type(u)
<type 'unicode'>
如果要把unicode符号保存到文件,或者是传输到网络,那就必须编码为str类型;反之亦然:
>>> u = u'禅'
>>> u
u'\u7985'
>>> u.encode('utf-8')
'\xe7\xa6\x85'
>>> s = '禅'
>>> s
'\xe7\xa6\x85'
>>> s.decode('utf-8')
u'\u7985'
说白了:
编码:字符到二进制数据的转换,即: encode: unicode → str
解码:二进制数据到字符的转换,即: decode: str → unicode
不同的编码之间通过unicode作为中间媒介来互相转换:
比如一个用utf-8编码好的汉字'\xe7\xa6\x85',要变成gbk:
'\xe7\xa6\x85'.decode('utf-8').encode('gbk')
case1:
>>> s = '你好'
>>> s.decode()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)
s.decode()默认使用ascii解码,但是ascii字符集中是没有中文字符的,因此报错: 0xe4超出范围了
case2:
>>> a = u'你好'
>>> a.encode()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
与case1类似,unicode转换str时默认用ascii,也是报错。
case3:
>>> s = '你好' # str类型
>>> y = u'python' # unicode类型
>>> s + y # 隐式转换,即 s.decode('ascii') + u
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)
str和unicode混用时,str会隐式地decode为unicode,然后也是由于用了ascii找不到汉字报错。这里详细见3.8节。
# coding: utf-8
>>> a='好'
>>> a
'\xe5\xa5\xbd'
>>> b=a.decode("utf-8")
>>> b
u'\u597d'
>>> c=b.encode("gbk")
>>> c
'\xba\xc3'
>>> print c
��
utf-8编码的字符‘好’占用3个字节,解码成Unicode后,如果再用gbk来解码后,只有2个字节的长度了,最后出现了乱码的问题,因此防止乱码的最好方式就是始终坚持使用同一种编码格式对字符进行编码和解码操作。
str()和unicode()是两个工厂方法,分别返回str字符串对象和unicode字符串对象。
其实本质为:
str(s) = s.encode('ascii')
unicode(s) = s.decode('ascii')
>>> s3 = u"你好"
>>> s3
u'\u4f60\u597d'
>>> str(s3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
对于一个unicode形式的str字符串,如\u4f60\u597d
这样的,如何变成真正的unicode呢?
最简单是前面加个u:
>>> print u'\u4f60\u597d'
你好
但是在像解析html时,储存在了一个字符串中,就可以这么做:
>>> s='\u4f60\u597d'
>>> type(s)
<type 'str'>
>>> s = s.decode('unicode-escape')
>>> print s
你好
可用chardet检测字符编码:
>>> import chardet
>>> a = '好'
>>> print chardet.detect(a)
{'confidence': 0.73, 'language': '', 'encoding': 'ISO-8859-1'}
Python2会在必要的情况下,对string作必要的编码类型转换,如==
操作,字节和字符拼接,以及对str编码(encode)时。
看3.4的例子,我们这么操作就不会报错了:
>>> import sys
>>> reload(sys)
<module 'sys' (built-in)>
>>> sys.setdefaultencoding('utf-8') # 初始化后删除了 sys.setdefaultencoding 方法,我们需要重新载入
>>> s = '你好'
>>> y = u'python'
>>> s + y
u'\u4f60\u597dpython'
s和y类型不一样,于是Python调用更改后的默认编码utf-8对s进行decode为unicode再操作。
再看:
>>> s='你好'
>>> s.encode('gb2312')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)
直接对str进行编码,首先会解码到unicode,默认ASCII,所以报错。
修改的方式为改成s.decode('utf-8').encode('gb2312')
或者加上sys.setdefaultencoding('utf-8')
注意: 用这种方法是有潜在危害的,详见: 立即停止使用 setdefaultencoding('utf-8'), 以及为什么
文中提到,好的习惯是:
- 所有 text string 都应该是 unicode 类型,而不是 str,如果你在操作 text,而类型却是 str,那就是在制造 bug。
- 在需要转换的时候,显式转换。从字节解码成文本,用 var.decode(encoding),从文本编码成字节,用 var.encode(encoding)。
- 从外部读取数据时,默认它是字节,然后 decode 成需要的文本;同样的,当需要向外部发送文本时,encode 成字节再发送。
Python2.7中调用print打印出var变量时,操作系统会对var做一定的字符处理:如果var是str类型的变量,则直接将var变量交付给终端进行显示;如果var变量是unicode类型,则操作系统首先将var编码成str类型的对象,再显示。
因此对于unicode中有中文,打印前一定要先encode('utf-8')之类,或者用sys.setdefaultencoding('utf-8')也行。