前言
“字符串”是Python非常常用的一种数据类型,当所有的内容全是英文的时候,世界静好;但是字符串中涉及到中文字符的时候,问题来了…
提到Python的字符串编码,我是这样的
不知道多少个夜晚,被Python的编码问题搞的心力交瘁生无可恋。好,现在我就要代表宇宙消灭你!!!
盘古开天辟地
计算机在最初发明的时候,只有127个常用字符被编码到计算机里,也就是26个英文字母大小写、数字、符号等,这种编码称为ASCII码。 每一个ASCII占用1字节的空间。
但是当计算机里需要显示中文的时候,ASCII码就不够用了,所以中国就制订了GB2312
编码,将中文编入;同时,如此渊博的中文,一个字节肯定不够表示啊,至少需要2个字节表示。
世界上有上百种语言,日本把日文编入Shift_JIS
里,韩国把韩文编到Euc-kr
里,所以在多语言混合处理的时候,难免会产生混乱。
秩序初现
于是,Unicode字符编码应运而生。
Unicode编码将各种语言统一到一套编码里,每个字符占用2个字节的空间(非常偏僻的字符需要用到4个),从而保证不会出现乱码问题,现代的操作系统和大多数编程语言都支持Unicode。
譬如,字母’A’的ASCII码是65,二进制是0100 0001
;’中’的编码已经超过了ASCII,用Unicode编码是20013,二进制是0100 1110 0010 1101
;
>>> ord("A")
65
>>> ord('中')
20013
>>>
而字母’A’使用Unicode编码,只需要在前面补0就可以了,即0000 0000 0100 0001
,所以Unicode是兼容ASCII码的。
发展前行
Unicode消除了乱码问题,新的问题又出现了:Unicode编码的字符占用2字节的存储空间,但如果文本全都是英文的话,使用Unicode比ASCII需要多一倍的存储空间,在存储和传输上就不太划算。
所以,UTF-8——可变长的Unicode编码出现了。UTF-8编码将英文字母编码为1个字节,中文字符编码为3个字节,而生僻字则可能使用4-6个字节,这样,在传输和存储的过程中则会更加节省空间。
所以,计算机中这些编码的运行过程是这样的:
- 在计算机内存中,统一使用Unicode编码
- 当需要存储或传输时,使用
encode
将Unicode编码为UTF-8,如u'中文'.encode('utf-8')
的意思是现有编码为Unicode,encode为参数所指示的编码 - 当从外存读数据进行运算时,则需要使用
decode
将UTF-8编码解码为为Unicode编码,如'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
的意思是现有编码为参数所指示的编码,decode为Unicode编码
搞清楚了各类编码的关系,接下来需要看看Python中具体的编码问题。
Python2.7中的字符编码
龟叔开发Python的时候,天下大一统的Unicode字符编码还没出来呢,所以最初的Python只支持ASCII编码。后来,才加入了对Unicode的支持,Unicode字符必须要使用u’….’来表示。
>>> u'中文'.encode('utf-8')
'\xe4\xb8\xad\xe6\x96\x87'
>>> u'ABC'.encode('utf-8')
'ABC'
>>> print '\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
中文
从中可以看出,utf-8编码和Unicode编码中英文字母的显示是一样的,但是如上所述,占用字节不一样,utf-8中占用1字节,而Unicode中占用2字节。
注意:不要使用window下的Python IDLE Shell测试代码,可能是它内部的编码都没有处理好,在cmd命令行中则一切ok。
什么时候会出错呢?
>>> '中文'.encode('utf-8')
Traceback (most recent call last):
File "<pyshell#3>", line 1, in <module>
'中文'.encode('utf-8')
UnicodeDecodeError: 'ascii' codec can't decode byte 0xd6 in position 0: ordinal not in range(128)
Bomb!是不是很熟悉,其实就是因为没有把Unicode编码使用u’…’包裹起来,导致系统以为它是ASCII编码,但是ASCII编码只有127个,肯定越界了。搞清楚了各大编码的关系,是不是解决问题就显得很简单(笑脸)?
Python3中的字符编码
在最新的Python 3版本中,字符串是以Unicode编码的,也就是说,我们不需要u’….’对Unicode字符进行包裹了。
Python2.7:
>>> '中文'
'\xd6\xd0\xce\xc4'
>>> u'中文'
u'\u4e2d\u6587'
>>> print u'中文'
中文
>>> print '中文'
中文
Python 3.5:
>>> '中文'
'中文'
>>> u'中文'
'中文'
由于Python的字符串类型是str,在内存中以Unicode表示,一个字符对应若干个字节。如果要在网络上传输,或者保存到磁盘上,就需要把str变为以字节为单位的bytes。Python对bytes类型的数据用带b前缀的单引号或双引号表示:
>>> b'ABC'
b'ABC'
>>> b'ABC'.decode('utf-8')
'ABC'
>>> '中文'.encode('utf-8')
b'\xe4\xb8\xad\xe6\x96\x87'
>>> b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
'中文'
需要区分b’ABC’与’ABC’,前者是utf-8编码的字节类型,每个字符占用一个字节;而后者是Python的字符串str类型。
在操作字符串时,我们经常遇到str和bytes的互相转换。为了避免乱码问题,应当始终坚持使用UTF-8编码对str和bytes进行转换。
由于Python源代码也是一个文本文件,所以,当你的源代码中包含中文的时候,在保存源代码时,就需要务必指定保存为UTF-8编码。当Python解释器读取源代码时,为了让它按UTF-8编码读取。
(By MrHammer 2016-05-12 下午4点 @Ningtu Sunny)