再来研究一下python中的字符编码

Posted by rogerclarkgc on 周日 23 七月 2017

Python2.x和Python3.x处理字符串的方式有了较大的改变,最近开始用Python3.6写项目,碰到了与2.x不同的编码问题,需要记下来注意一下

Python中的字符编码模型

2.x中采用的默认编码方式是ASCII,这种标准只定义了0~127的字符代码,每个代码映射一个字符,在内存中,这些字符代码能存储在一个8位的字节里,对于西欧地区,往往128个字符代码不够使用,所以又额外定义了128~255的字符代码(8位字节能表示0~255的值),这个增补的字符代码加上原来的128个代码构成的编码标准为Latin-1。对于一些字母表很多的语言或者非音标的语言,如汉语,仅仅一个8位字节不够编码所有字母或字符,因此,诞生了Unicode来使用多个字节来表示字符,这种用Unicode编码的文本通常叫“宽字符”字符串,许多亚洲或欧洲字符集用Unicode在计算机中进行编码。

计算机用编码和解码两种操作来完成字符和原始字符代码(字节码)之间的互相转化,这里是这两种操作的定义:

  • 编码是根据一个想要的编码规则(如ASCII),把一个字符串翻译为原始字节码
  • 解码是根据已知的编码规则,把一个原始的字节码翻译成字符串的形式

对于只是用26个英文字符的语言,这一过程在许多计算机中都能正确的执行,我们往往能在屏幕上,或者写入的文件中看到正确的结果,因为ASCII是许多编码规则的子集,所以在转换中是能够正确识别的,但对于汉字或其他语言的文本,它们要不是非单字节编码集所编码的,要不是非0~127字符代码编码的,因此在读写操作时,往往会由于系统默认的编码集与文本的实际编码集不兼容而导致常见的乱码错误。

Python中的字符串类型

2.x系列和3.x系列有不同的字符串类型,这里分开讲述:

对于2.x系列,有两种类型来表示字符

  • str表示8位的文本和二进制数据
  • unicode表示宽字符Unicode文本

可以看到,这种分类方式也是让人疑惑的起源,首先str类既能表示类似ASCII编码下的8位文本,又能表示二进制的字节码,而unicode类只用来表示宽字符文本,这样许多文本操作对于两类来说是通用的,然而str类表示的二进制字节码却和unicode类和8字节的str类不同,它们都是未经过解码的原生字节码,这种一个类型却有两种截然不同的数据对象的封装是让人费解的。

在3.x系列中,明确了二进制数据了字符串文本之间的区别,并将它们分开封装,一共有三种类型来表示:

  • str文本表示Unicode文本(8位和宽字符)
  • bytes表示二进制的字节码数据
  • bytearray,一种可变的bytes类型

这种新的类型就解决了2.x系列中存在已久的疑惑,原来的unicode类整合进str类了,并且原来属于str类的二进制数据被单独分开成bytes类,这样确实更加符合使用的逻辑,本来二进制数据和字符串就不是两种数据,并且ASCII和其他8位文本本身就应该是一种简单的Unicode。

要在python3.x中使用新的bytes类型,可以用b''显式指定一个二进制的字节码常量:

>>>b = b'bytes'
>>>s = 'string'
>>>type(b), type(s)
(<class 'bytes'>, <class 'str'>)

bytes对象和str对象一样都是不可变的,但是它都支持列表的索引操作,但是bytes实际上是一个存储了字节码的整数列表:

>>> type(b), type(s)
(<class 'bytes'>, <class 'str'>)
>>> b[0],s[0]
(98, 's')
>>> b[0:2], s[0:2]
(b'by', 'st')
>>> list(b), list(s)
([98, 121, 116, 101, 115], ['s', 't', 'r', 'i', 'n', 'g'])

encode()和decode()

Python3.x中由于明确了bytes和str的类型,其encode()decode()方法与2.x中有了很大的不同。

2.x的编码转换

在2.x中,由于str是8位字符或字节码,unicode类是宽字符,所以str类能使用encode()decode()方法:

>>> a = '中文'
>>> a, type(a)
('\xd6\xd0\xce\xc4', <type 'str'>)
>>> a.decode('gbk'), type(a.decode('gbk'))
(u'\u4e2d\u6587', <type 'unicode'>)

使用decode()方法能将str转化成unicode类,在转化前需要指定当前字符采用的编码集。

>>> c = a.decode('gbk').encode('utf-8')
>>> c
'\xe4\xb8\xad\xe6\x96\x87'
>>> print(c)
中文
>>> c, type(c)
('\xe4\xb8\xad\xe6\x96\x87', <type 'str'>)

使用encode()方法能够将unicode类转化成指定编码下的str类,上面先将字符串依照gbk编码集转化成unicode再重新编码成utf-8编码下的字符串。

总得来说,2.x下的编码转化仅仅在str和unicode之间进行,不涉及字节码。

3.x的编码转换

在3.x中的编码转换更加符合逻辑上的定义,即:

str类-->encode()-->bytes类

bytes类-->decode()-->str类

由于明确了字节码与字符串的区别,现在str类只能使用encode方法进行编码,转换成bytes类字节码,而反过来,bytes类通过decode方法进行解码,转换成字符串str类。

>>> c
'字节码'
>>> c.encode('gbk')
b'\xd7\xd6\xbd\xda\xc2\xeb'
>>> c.encode('utf-8')
b'\xe5\xad\x97\xe8\x8a\x82\xe7\xa0\x81'
>>> c, type(c)
('字节码', <class 'str'>)
>>> c.encode('gbk'), c.encode('utf-8'), type(c.encode('utf-8'))
(b'\xd7\xd6\xbd\xda\xc2\xeb', b'\xe5\xad\x97\xe8\x8a\x82\xe7\xa0\x81', <class 'bytes'>)
>>> list(c.encode('utf-8')), list(c.encode('utf-8'))
([229, 173, 151, 232, 138, 130, 231, 160, 129], [229, 173, 151, 232, 138, 130, 231, 160, 129])

使用encode方法时,默认会转换成当前环境的编码方式,可以手动指定一个编码方式。

>>> b = c.encode('gbk')
>>> b
b'\xd7\xd6\xbd\xda\xc2\xeb'
>>> b.decode()
Traceback (most recent call last):File "<pyshell#22>", line 1, in <module>
b.decode()
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd7 in position 0: invalid continuation byte
>>> b.decode('gbk')
'字节码'

对字节码bytes对象使用decode()方法时,如果没有指定对应的字符集,那么会按照环境默认的字符集解码,这里我们先用gbk编码了字符串,但由于系统默认采用utf-8编码,所以再解码时无法对字节码进行解码,出现UnicodeDecodeError。在指定了正确的字符集后,能够正确执行。

tags: python