blog/docs/code/python/code.md

11 KiB
Raw Blame History

title date tags categories author
编码 2020-07-11 14:08:58
Python
Python
Anges黎梦

在编写python程序的过程中中英文混用经常会出现编码问题。

围绕此问题本文首先介绍编码的含义及常用编码随后列举几个python经常遇到的编码异常及解决方法

接着列举笔者在实践中遇到的异常出现的情景及原因,最后针对编码问题提出最佳实践。

常见编码

unicode编码

A: 00000010 00000010 00000010 00000010  4个字节32位。
中: 00000010 00000010 00000010 00000010  4个字节32位
万国码,包含所有国家的文字。

在文本文件中看到的所有字符包括中文都需要在计算机中存储而计算机只能存储0和1这样的二进制位

所以需要一种方法,将字符映射成数字,然后将数字转化为二进制位存储在计算机中。

针对字符和数字的映射的问题产生了unicode编码unicode将世界上的所有字符映射为唯一的数字。

unicode数字并不是直接就可以转化为二进制存储比如假设中文字符映射为数字100000001

映射为数字200000010由于汉字很多单字节并不能表示完所有的汉字

故可能会有汉字的unicode数字为25800000001 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可以表示的字符如英文字符AB在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字符。

python2中的另一种字符序列是str类型str类型的字符序列其实是unicode字符序列encode之后的值

用不同的编码类型encode得出的值不一样。str字符序列的元素为字节

如图所示,“中国” 的str字符序列长度为6为UTF-8编码后所占字节长度。

与unicode字符串转化为str类型用encode相反str类型的字符序列转化为unicode字符串

可以通过decode方法如图所示

python3中的字符序列也有两种类型bytes和str。python3中的bytes和python2中的str相似

str和python2中的unicode相似。这里要注意,str类型在python3和python2中都有但含义完全变了。

常见编码问题

UnicodeEncoderError

将文本转化为字节序列时若有字符在目标编码中没有定义则会出现UnicodeEncoderError。

如图所示由于中文字符在ascii编码中无定义则会报出编码错误。

对于此类问题需选择合适的编码类型比如含有中文字符一般用UTF-8编码类型对unicode字符串编码。

UnicodeDecodeError

把二进制序列转化为文本时,遇到无法转换的字节序列,则会发生此异常。

比如用UTF-8编码后的二进制序列用GB2312解码由于两种编码不兼容用GB2312不能识别字节序列

则会出现异常,如图所示。

碰到这种异常是由于decode使用的编码和字节序列的编码不一致

可以用字符编码侦测包chardet检测字节序列的编码然后再用此编码解码。如图所示:

实践中常见编码异常场景

字符串连接

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编码不一致终端就会显示出乱码。

最佳实践

编写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) #<type 'str'>
23     value = to_unicode(value)
24     print type(value) #<type 'unicode'>
#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)) #<class 'bytes'>
    value = to_str(value)
    print(type(value)) #<class 'str'>