Python学习之路(23)——字符编码和字符串
字符编码
在Python中,字符串也是一种数据类型,而字符串特殊的还有一个字符编码的问题。
由于计算机智能处理数字,如果要处理文本,就必须先将文本转为数字然后处理。早期的计算机在设计时采用8个比特位(bit)作为一个字节(byte),所以一个字节能表示的最大整数是255,如果要表示更大的整数,就必须使用更多的字节。
美国人在发明计算机时,最早只将127个字符被编码到计算机里,也就是大小写英文字母、数字和一些符号,这个编码表被称为ASCII编码,比如A的ASCII码为65,z的ASCII码为122。
但是世界上的字符数不胜数,各国也有各国的专属字符,比如要处理中文显然一个字节是远远不够的,至少需要两个字节(最大整数位65535),而且不能和ASCII编码冲突,所以中国制定GB2312编码,用来将中文编入。
另外还有日文的Shift_JIS编码和韩国的Euc-kr编码等,各国都有各国的标准,就会不可避免的出现冲突,结果就是在多语言混合的文本中,显示就会有乱码。
解决方案就是Unicode,Unicode将所有语言统一到一套编码中。Unicode的标准也在不断发展,但最常用的是用两个字节表示一个字符(如果要用到非常偏僻的字符时,就需要四个字节),基本上现代操作系统和大多数编程语言都直接支持Unicode。
ASCII编码和Unicode编码的区别:
ASCII编码是1个字节,Unicode编码通常是2个字节。
比如:
字母A用ASCII编码是十进制的65,二进制的01000001
字符0用ASCII编码是十进制的48,二进制的00110000
汉字“中”超出了ASCII编码范围,用Unicode编码是十进制的20013,二进制的01001110 00101101
那么,可以想象得到,如果把ASCII编码的字母A用Unicode编码,只需要在前面补0即可,即字母A的Unicode编码为00000000 01000001。
接着又出现新的问题:统一成Unicode编码,乱码问题确实消失了,但是如果文本都是英文,使用Unicode编码比使用ASCII编码需要多一倍的存储空间,性价比就差了。
因此,出现了把Unicode编码转化为“可变长编码”的UTF-8编码。
UTF-8编码把一个Unicode字符根据不同的数字大小编码成1-6个字节,常用的英文字母被编码成1个字节,汉字通常是3个字节,只有很生僻的字符才会被编码成4-6个字节。
如下:
| 字符 | ASCII | Unicode | UTF-8 |
|---|---|---|---|
| A | 01000001 | 00000000 01000001 | 01000001 |
| 中 | --- | 01001110 00101101 | 11100100 10111000 10101101 |
从上面的变革可以发现,UTF-8编码有一个额外的好处,就是ASCII编码实际上可以被看成是UTF-8编码的一部分。
另外,从上面的表格可以看出,Unicode编码和UTF-8编码有很大的不同。那么,Unicode编码和UTF-8编码之间是如何编码的呢?

还是以上面的汉字“中”为例,“中”的Unicode编码是U+4e2d。显然是位于上表中第三行的U+0800~U+FFFF之间,U+4e2d的16进制编码为4E2D,将其转换为二进制为01001110 00101101。然后将得到的二进制编码从左到右依次替换到1110xxxx 10xxxxxx 10xxxxxx的x位置上,最终得到一串二进制数据 11100100 10111000 10101101,这串数据就是汉字“中”对应的UTF-8编码。
Python的字符串
Python 3版本中,字符串是以Unicode编码的,也就是说Python的字符串支持多语言:
>>> print('包含中文的str')
包含中文的str
对于单个字符的编码,ord()函数获取字符的整数表示,chr()函数把整数编码转换为对应的字符:
>>> ord('A')
65
>>> ord('中')
20013
>>> chr(66)
'B'
>>> chr(25991)
'文'
如果知道字符的整数编码,还可以用十六进制:
>>> '\u4e2d\u6587' '中文'
由于Python的字符串类型是str,在内存中以Unicode表示,一个字符对于若干个字节。如果在网络上传输或者保存到磁盘上,需要将str变为以字节为单位的bytes。Python中对bytes类型的数据用带b前缀的单引号或双引号表示:
>>> x = b'ABC' >>> x b'ABC'
需要区分'ABC'和b'ABC',前者是字符串str,后者是bytes,每个字符都只占用一个字节。
以Unicode表示的str通过encode()方法可以编码为指定的bytes:
>>> 'ABC'.encode('ASCII')
b'ABC'
>>> 'ABC'.encode('UTF-8')
b'ABC'
>>> '中文'.encode('ASCII')
Traceback (most recent call last):
File "<pyshell#236>", line 1, in <module>
'中文'.encode('ASCII')
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
>>> '中文'.encode('UTF-8')
b'\xe4\xb8\xad\xe6\x96\x87'
纯英文的str可以用ASCII编码为bytes,内容是一样的,含有中文的str可以用UTF-8编码为bytes(不能用ASCII编码)。在bytes中,无法显示为ASCII字符的字节,用\x##显示。
反过来,如果从网络或磁盘上读取了字节流,那么读到的数据就是bytes,使用decode()方法将bytes变为str:
>>> b'ABC'.decode('ASCII')
'ABC'
>>> b'\xe4\xb8\xad\xe6\x96\x87'.decode('UTF-8')
'中文'
但是如果bytes中包含无法解码的字节,decode()方法会报错:
>>> b'\xe4\xb8\xad\xff'.decode('UTF-8')
Traceback (most recent call last):
File "<pyshell#248>", line 1, in <module>
b'\xe4\xb8\xad\xff'.decode('UTF-8')
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 3: invalid start byte
上面说明第3个position的0xff无法解码,那么我们可以传入errors='ignore'进行忽略错误的字节:
>>> b'\xe4\xb8\xad\xff'.decode('UTF-8', errors = 'ignore')
'中'
接下来,我们使用len()函数计算字符串str的字符数:
>>> len('ABC')
3
>>> len('中文')
2
len()函数是计算str的字符数,如果换成bytes:
>>> len(b'AC')
2
>>> len(b'\xe3\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编码读取源代码。
浙公网安备 33010602011771号