--- title: 编码 date: 2020-07-11 14:08:58 tags: [Python] categories: [Python] author: Anges黎梦 --- 在编写python程序的过程中,中英文混用经常会出现编码问题。 围绕此问题,本文首先介绍编码的含义及常用编码,随后列举几个python经常遇到的编码异常及解决方法, 接着列举笔者在实践中遇到的异常出现的情景及原因,最后针对编码问题提出最佳实践。 ## 常见编码 ### unicode编码 ``` A: 00000010 00000010 00000010 00000010 4个字节,32位。 中: 00000010 00000010 00000010 00000010 4个字节,32位 万国码,包含所有国家的文字。 ``` 在文本文件中,看到的所有字符,包括中文,都需要在计算机中存储,而计算机只能存储0和1这样的二进制位, 所以需要一种方法,将字符映射成数字,然后将数字转化为二进制位存储在计算机中。 针对字符和数字的映射的问题,产生了unicode编码,unicode将世界上的所有字符映射为唯一的数字。 unicode数字并不是直接就可以转化为二进制存储,比如假设中文字符‘中’映射为数字1(00000001), ‘国’映射为数字2(00000010),由于汉字很多,单字节并不能表示完所有的汉字, 故可能会有汉字的unicode数字为258(00000001 00000010), 假设为‘京’,现在在字符串中碰到存储为00000001 00000010的二进制串,不能区分出其实际代表的是“中国”还是“京”。 针对unicode数字和二进制的映射问题, 有两种解决方法: - 一种是每个unicode数字用固定宽度的二进制位表示,比如都用两字节,由此产生了ASCII、GB2312、GBK编码; - 另一种是存储的二进制位除了表示数字之外,还表示每个unicode数字的长度,由此产生了utf-8编码。 ### ASCII编码 ASCII编码用单字节表示字符,最高位固定为0,故最多只能表示128个字符,当编程只涉及到英文字符或数字时, 不涉及中文字符时,可以使用ASCII编码。 ``` A: 00000010 一个字节,8位。只有英文、数字、符号。 最后一位是0。7位就够了,最后一位是预留扩展用的。 ``` ### GB2312编码、GBK GB(GuoBiao)为国标,GBK(GuoBiao Kuozhan)表示国标扩展。 GB2312兼容ASCII编码,对于ASCII可以表示的字符,如英文字符‘A’、‘B’等,在GB2312中的编码和ASCII编码一致, 占一个字节,对于ASCII不能表示的字符,GB2312用两个字节表示,且最高位不为0,以防和ASCII字符冲突。 例如:‘A’在GB2312中存储的字节十六进制为41,在ASCII中也是41,中文字符‘中’在GB2312中存储的两个字节十六进制为D6D0,最高位为1不为0。 GB2312只有6763个汉字,而汉字特别多。 GBK属于GB2312的扩展,增加了很多汉字,同时兼容GB2312,同样用两个字节表示非ASCII字符。 ``` A: 00000010 一个字节 8位 中: 00000010 00000010 2个字节,16位 是国人开发的只包含大多数中文和英文。 (ps:中文一共有9w+的汉字,而16位只能包含6w+的汉字) ``` ### UTF-8编码 和GB系列不同,UTF-8可以将全世界所有的unicode数字表示出来。 UTF-8兼容ASCII编码,不兼容GB系列编码,因此,若文本中UTF-8和GB系列编码混用,会出现乱码问题。 UTF-8对于每个字符的存储,用最高二进制位开始连续1的个数表示字的长度,最高位为0表示单字节, 用来兼容ASCII字符,为110表示双字节,非字符首字节的字节都以10开始,如下表格所示。 例如:字符‘中’的unicode编码为2D4E(00101101 01001110),用UTF-8存储的二进制为E4B8AD(11100100 10111000 10101101 ), 存储在计算机中的首字节为1110开头,表示此字符占三个字节,去掉开始字节表示长度的1110和其余字节开头的10, 可以得到01001110 00101101(4E2D),可以看到和unicode数字刚好相反,是因为是大端存储方式, 高字节存储在内存中的低地址端,反过来即为unicode编码。 ``` A: 00000010 一个字节 8位 中: 00000010 00000010 00000010 3个字节,24位 是Unicode的升级优化版。 ``` 字节数|二进制编码格式 --:|---: 单字节|0XXXXXXX 双字节|110XXXXX 10XXXXXX 三字节|1110XXXX 10XXXXXX 10XXXXXX 四字节|11110XXX 10XXXXXX 10XXXXXX 10XXXXXX 五字节|111110XX 10XXXXXX 10XXXXXX 10XXXXXX 10XXXXXX 六字节|1111110X 10XXXXXX 10XXXXXX 10XXXXXX 10XXXXXX 10XXXXXX ## python字符序列及编码问题 上一节对几种常见的编码原理做出了介绍,以便理解python由于编码引起的异常, 本节将对python中的字符串作出介绍,并在此基础上提出几种常见的编码异常,并提供解决方案。 ### python2和python3字符序列 python2中字符序列有两种类型:unicode和str。unicode字符序列存储的元素为unicode字符。 如图所示,unicode_string代表unicode字符序列“中国”,其长度为2,恰好表示两个unicode字符。 ![](https://limeng-blog.oss-cn-hangzhou.aliyuncs.com/code/python-code/1.png) python2中的另一种字符序列是str类型,str类型的字符序列其实是unicode字符序列encode之后的值, 用不同的编码类型encode,得出的值不一样。str字符序列的元素为字节, 如图所示,“中国” 的str字符序列长度为6,为UTF-8编码后所占字节长度。 ![](https://limeng-blog.oss-cn-hangzhou.aliyuncs.com/code/python-code/2.png) 与unicode字符串转化为str类型用encode相反,str类型的字符序列转化为unicode字符串, 可以通过decode方法,如图所示: ![](https://limeng-blog.oss-cn-hangzhou.aliyuncs.com/code/python-code/3.png) python3中的字符序列也有两种类型:bytes和str。python3中的bytes和python2中的str相似, str和python2中的unicode相似。这里要注意,str类型在python3和python2中都有,但含义完全变了。 ![](https://limeng-blog.oss-cn-hangzhou.aliyuncs.com/code/python-code/4.png) ### 常见编码问题 #### UnicodeEncoderError 将文本转化为字节序列时,若有字符在目标编码中没有定义,则会出现UnicodeEncoderError。 如图所示,由于中文字符在ascii编码中无定义,则会报出编码错误。 对于此类问题,需选择合适的编码类型,比如含有中文字符,一般用UTF-8编码类型对unicode字符串编码。 ![](https://limeng-blog.oss-cn-hangzhou.aliyuncs.com/code/python-code/5.png) #### UnicodeDecodeError 把二进制序列转化为文本时,遇到无法转换的字节序列,则会发生此异常。 比如用UTF-8编码后的二进制序列,用GB2312解码,由于两种编码不兼容,用GB2312不能识别字节序列, 则会出现异常,如图所示。 ![](https://limeng-blog.oss-cn-hangzhou.aliyuncs.com/code/python-code/6.png) 碰到这种异常,是由于decode使用的编码和字节序列的编码不一致, 可以用字符编码侦测包chardet检测字节序列的编码,然后再用此编码解码。如图所示: ![](https://limeng-blog.oss-cn-hangzhou.aliyuncs.com/code/python-code/7.png) ## 实践中常见编码异常场景 ### 字符串连接 python代码 ``` 1 # -*- coding: utf-8 -*- 2 unicode_string=u'中国' 3 str_string='中国' 4 merge_string= str_string+unicode_string #UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128) ``` python代码 ``` 1 # -*- coding: utf-8 -*- 2 unicode_string=u'中国' 3 str_string='中国' 4 "中国:%s" % str_string 5 #两种字符序列混用,相当于"中国:%s".decode('ascii')%unicode_string 6 "中国:%s" % unicode_string #UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128) 7 u"中国:%s"%unicode_string 8 #两种字符序列混用,相当于u"中国:%s"%str_string.decode('ascii') 9 u"中国:%s"%str_string #UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128) ``` 当str类型字符串和unicode类型字符串混合运算时,python默认会将str类型字符串转化为unicode字符串, 由于不知道str类型字符串的编码格式,会使用 sys.getdefaultencoding() , 而默认的defaultencoding一般是ascii,故会出错。 ### print中文问题 如图,python打印变量时,操作系统会对变量进行相应的处理,若变量是str类型,则操作系统直接发送到终端显示, 若变量是unicode类型,则操作系统会对变量用sys.stdout.encoding编码对变量encode, 若变量中含有sys.stdout.encoding未定义字符,则会出现UnicodeEncodeError。 编码后字节序列被发送给终端,假若终端设置的编码和str编码不一致,终端就会显示出乱码。 ![](https://limeng-blog.oss-cn-hangzhou.aliyuncs.com/code/python-code/8.png) ## 最佳实践 编写python程序时,为避免不同类型字符串混用出现编解码异常,要把编码和解码操作放在程序的最外围来做, 程序的核心逻辑统一使用unicode字符类型。下面分别对python2和python3编写了外围编码转换工具类。 ``` 1 #python2,unicode和utf-8类型的str互相转换 2 #file:python2_endecode_helper.py 3 4 # -*- coding: utf-8 -*- 5 def to_unicode(unicode_or_str): 6 if isinstance(unicode_or_str, str): 7 value = unicode_or_str.decode('UTF-8') 8 else: 9 value = unicode_or_str 10 return value 11 12 def to_str(unicode_or_str): 13 if isinstance(unicode_or_str, unicode): 14 value = unicode_or_str.encode('UTF-8') 15 else: 16 value = unicode_or_str 17 return value 18 19 if __name__=='__main__': 20 unicode_string = u'中国' 21 value = to_str(unicode_string) 22 print type(value) # 23 value = to_unicode(value) 24 print type(value) # ``` ``` #python3,str和bytes类型相互转换工具类 #file:python3_endecode_helper.py def to_str(bytes_or_str): if isinstance(bytes_or_str,bytes): value = bytes_or_str.decode('UTF-8') else: value = bytes_or_str return value def to_bytes(bytes_or_str): if isinstance(bytes_or_str,str): value = bytes_or_str.encode('UTF-8') else: value = bytes_or_str return value if __name__=='__main__': str_string = u'中国' value = to_bytes(str_string) print(type(value)) # value = to_str(value) print(type(value)) # ```