1 字符编码简介
1.1 ASCII
ASCII:American Standard Code for Information Interchange。
计算机是美国人发明的,因此最早只有127个字符被编码到计算机里,也就是大小写英文字母、数字和一些符号,这个编码表被称为ASCII编码,比如大写字母A的编码是65,小写字母a的编码是97。
ASCII码使用指定的7位或8位二进制数组合来表示128或256种可能的字符。标准ASCII码也叫基础ASCII码,使用7位二进制数(剩下的1位二进制为0)来表示所有的大写和小写字母,数字0到9、标点符号,以及在美式英语中使用的特殊控制字符。
后128个称为扩展ASCII码。许多基于x86的系统都支持使用扩展(或“高”)ASCII。扩展ASCII码允许将每个字符的第8位用于确定附加的128个特殊符号字符、外来语字母和图形符号。
但是要处理中文显然一个字节是不够的,至少需要两个字节,而且还不能和ASCII编码冲突,所以中国制定了GB2312编码,用来把中文编进去。
全世界有上百种语言,日文编到Shift_JIS里,韩文编到Euc-kr里,各国有各国的标准,就不可避免地出现冲突,结果就是,在多语言混合的文本中,显示出来会有乱码。
1.2 Unicode
有人觉得太多编码导致世界变得过于复杂,于是想出来一个方法:所有语言的字符都用同一种字符集来表示,这就是Unicode。可以想象,如果有一种编码,将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码,那么乱码问题就会消失。
1.3 UTF-8
需要注意的是,Unicode只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。这里就产生了两个严重的问题:
- 计算机要如何区分Unicode编码和ASCII编码呢?
- 对英文字母来说,用Unicode编码时,每个字符使用三个、四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这是无法接受的。
UTF-8就是在互联网上使用最广的一种Unicode的实现方式。其他实现方式还包括UTF-16(字符用两个字节或四个字节表示)和UTF-32(字符用四个字节表示),不过基本不用。
UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1-4个字节表示一个符号,根据不同的符号而变化字节长度。
ASCII编码实际上可以被看成是UTF-8编码的一部分,所以,大量只支持ASCII编码的历史遗留软件可以在UTF-8编码下继续工作。
2 字符与字节
2.1 字符与字节
一个字符不等价于一个字节,字符是人类能够识别的符号,而这些符号要保存到计算机的存储中,就需要用计算机能够识别的字节来表示。
一个字符往往有多种表示方法(字符编码),不同的表示方法会使用不同的字节数。比如字母A-Z都可以用ASCII码表示(占一个字节),也可以用Unicode表示(占两个字节),还可以用UTF-8表示(占一个字节)。
字符编码的作用就是将人类可识别的字符转换为机器可识别的字节码,解码就是将机器可识别的字节码转换成人类可识别的字符。
Unicode才是真正的字符串,而用ASCII、UTF-8、GBK等字符编码表示的是字节串。从上面对各种编码方式的介绍中,我们也可以了解到,像ASCII、UTF-8、GBK这些都是编码方式,是将字符编码为字节码。Unicode只是一个符号集,它只规定了符号的二进制代码,也就是说它给每一个字符一个独一无二的数字来表示。
2.2 编码与解码
编码(encode):在Unicode中,每一个字符都有一个唯一的数字表示,那么将Unicode字符串转换为特定字符编码(ASCII、UTF-8、GBK)对应的字节串的过程和规则就是编码。
解码(decode):将特定字符编码(ASCII、UTF-8、GBK)的字节串转换为对应的Unicode字符串的过程和规则就是解码。
简单理解:编码是给计算机底层用的,解码是显示给人看的。
3 Python中的默认编码
3.1 Python源代码文件的执行过程
我们都知道,磁盘上的文件都是以二进制格式存放的,其中文本文件都是以某种特定编码的字节形式存放的。对于程序源代码文件的字符编码是由编辑器指定的,比如我们使用Pycharm来编写Python程序时会指定工程编码和文件编码为UTF-8,那么Python代码被保存到磁盘时就会被转换为UTF-8编码对应的字节(encode过程)后写入磁盘。
当执行Python代码文件中的代码时,Python解释器在读取Python代码文件中的字节串之后,需要将其转换为Unicode字符串(decode过程)之后才执行后续操作。
3.2 默认编码
如果我们没有在代码文件指定字符编码,Python解释器会使用哪种字符编码把从代码文件中读取到的字节转换为Unicode字符串呢?就像我们配置某些软件时,有很多默认选项一样,需要在Python解释器内部设置默认的字符编码来解决这个问题,这就是“默认编码”。
Python2和Python3的解释器使用的默认编码是不一样的,我们可以通过sys.getdefaultencoding()来获取默认编码:
>>> # Python2
>>> import sys
>>> sys.getdefaultencoding()
'ascii'
>>> # Python3
>>> import sys
>>> sys.getdefaultencoding()
'utf-8'
对于Python2来讲,Python解释器在读取到中文字符的字节码时,会先查看当前代码文件头部是否指明字符编码是什么。如果没有指定,则使用默认字符编码"ASCII"进行解码,导致中文字符解码失败,出现如下错误:
SyntaxError:Non-ASCII character '\xc4' in file xxx.py on line 11, but no encoding declared;
see http://python.org/dev/peps/pep-0263/ for details
对于Python3来讲,执行过程是一样的,只是Python3的解释器以"UTF-8"作为默认编码,但是这并不表示可以完全兼容中文问题。比如我们在Windows上进行开发时,Python工程及代码文件都使用的是默认的GBK编码,也就是说Python代码文件是被转换成GBK格式的字节码保存到磁盘中的。Python3的解释器执行该代码文件时,试图用UTF-8进行解码操作时,同样会解码失败,出现如下错误:
SyntaxError:Non-UTF-8 code starting with '\xc4' in file xx.py on line 11, but no encodingdeclared;
see http://python.org/dev/peps/pep-0263/ for details
4 Python2、Python3对字符串的支持
4.1 Python2
Python2中对字符串的支持由以下三个类提供:
class basestring(object)
class str(basestring)
class unicode(basestring)
str和unicode都是basestring的子类。严格意义上说,str其实是字节串,它是unicode经过编码后的字节组成的序列。对UTF-8编码的str'汉'使用len()函数时,结果是3,因为UTF-8编码的'汉'=='\xE6\xB1\x89'。
unicode才是真正意义上的字符串,对字节串str使用正确的字符编码进行解码后获得,并且len(u'汉')==1。
#!/usr/bin/env python
#-*- coding:utf-8 -*-
a = '你好'
b = u'你好'
print(type(a),len(a)) # output:(<type'str'>, 6)
print(type(b),len(b)) # output:(<type'unicode'>, 2)
4.2 Python3
Python3中对字符串的支持进行了实现类层次的上简化,去掉了unicode类,添加了一个bytes类。从表面上来看,可认为Python3中的str和unicode合二为一了。
class bytes(object)
class str(object)
实际上,Python3中已经意识到之前的错误,开始明确区分字符串与字节。因此Python3中的str已经是真正的字符串,而字节是用单独的bytes类来表示。
也就是说,Python3默认定义的就是字符串,实现了对Unicode的内置支持,减轻了程序员对字符串处理的负担。
#!/usr/bin/env python
#-*- coding:utf-8 -*-
a = '你好'
b = u'你好'
c = '你好'.encode('gbk')
print(type(a),len(a)) # output:<class'str'> 2
print(type(b),len(b)) # output:<class'str'> 2
print(type(c),len(c)) # output:<class'bytes'> 4
4.3 比较
对于单个字符的编码,Python提供了ord()函数获取字符的整数表示,chr()函数把编码转换为对应的字符:
>>> ord('A')
65
>>> ord('中')
20013
>>> chr(97)
'a'
>>> chr(20013)
'中'
如果知道字符的整数编码,还可以用十六进制这么写str:
>>> '\u4e2d\u6587'
'中文'
两种写法完全是等价的。
由于Python的字符串类型是str,在内存中以Unicode表示,一个字符对应若干个字节。如果要在网络上传输,或者保存到磁盘上,就需要把str变为以字节为单位的bytes。
Python对bytes类型的数据用带b前缀的单引号或双引号表示:x = b'ABC'。
要注意区分'ABC'和b'ABC',前者是str,后者虽然内容显示得和前者一样,但bytes的每个字符都只占用一个字节。
以Unicode表示的str通过encode()方法可以编码为指定的bytes,例如:
>>> 'ABC'.encode('ascii')
b'ABC'
>>> '中文'.encode('utf-8')
b'\xe4\xb8\xad\xe6\x96\x87'
>>> '中文'.encode('ascii')
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)
纯英文的str可以用ASCII编码为bytes,内容是一样的,含有中文的str可以用UTF-8编码为bytes。含有中文的str无法用ASCII编码,因为中文编码的范围超过了ASCII编码的范围,Python会报错。
在bytes中,无法显示为ASCII字符的字节,用\x##显示。
反过来,如果我们从网络或磁盘上读取了字节流,那么读到的数据就是bytes。要把bytes变为str,就需要用decode()方法:
>>> b'ABC'.decode('ascii')
'ABC'
>>> b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
'中文'
要计算str包含多少个字符,可以用len()函数:
>>> len('ABC')
3
>>> len('中文')
2
len()函数计算的是str的字符数,如果换成bytes,len()函数就计算字节数:
>>> len(b'ABC')
3
>>> len(b'\xe4\xb8\xad\xe6\x96\x87')
6
>>> len('中文'.encode('utf-8'))
6
可见,1个中文字符经过UTF-8编码后通常会占用3个字节,而1个英文字符只占用1个字节。
在操作字符串时,我们经常遇到str和bytes的互相转换。为了避免乱码问题,应当始终坚持使用UTF-8编码对str和bytes进行转换。
当Python解释器读取源代码时,为了让它按UTF-8编码读取,我们通常在文件开头写上这两行:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
第一行注释是为了告诉Linux/OS X系统,这是一个Python可执行程序,Windows系统会忽略这个注释。
第二行注释是为了告诉Python解释器,按照UTF-8编码读取源代码,否则,你在源代码中写的中文输出可能会有乱码。
5 字符编码的转换
Unicode字符串可以与任意字符编码的字节串进行相互转换,如图:
从上图可以看出不同字节编码之间是可以通过Unicode来实现相互转换的。
Python2中的字符串进行字符编码转换过程是:
字节串(Python2的str默认是字节串)-->decode('原来的字符编码')-->Unicode字符串-->encode('新的字符编码')-->字节串
#!/usr/bin/env python2
#-*- coding:utf-8 -*-
utf_8_a = '我爱中国'
gbk_a = utf_8_a.decode('utf-8').encode('gbk')
print(gbk_a.decode('gbk'))
# 输出结果:我爱中国
Python3中定义的字符串默认就是unicode,因此不需要先解码,可以直接编码成新的字符编码:
字符串(str就是Unicode字符串)-->encode('新的字符编码')-->字节串
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
utf_8_b = '我爱中国'
gbk_b = utf_8_b.encode('gbk')
print(gbk_b.decode('gbk'))
# 输出结果:我爱中国
从上图中,也可看出ASCII编码实际上可以被看成是UTF-8编码的一部分。
如果您发现文中有不清楚或者有问题的地方,请在下方评论区留言,我会根据您的评论,更新文中相关内容,谢谢!