python编码深度解析

原创
2019/10/23 08:57
阅读数 84

python编码深度解析

一、字符、字节、编码

1.1 为什么编码?

编码就是为了解决字符和字节之间的关系。

字节字符

1.2 为什么要分字符和字节?

分字符和字节是为了解决人机之间的关系,一般人只能读字符,机器只能识别字节。

1.3 字符和编码之间的关系?

字符'a',人能够识别,机器不能识别,机器只能识别字节。

所以需要一种把字符'a'转换为字节的技术。

这种技术就是编码,例如我们最常见的ASCII(American Standard Code for Information Interchange)码,就包含英文字符数字和一些控制字符。

我们通过ASCII码表就可以找到字符'a'对应的数字为97,这样我们就可以把字符'a'通过编码转换为二进制字节01100001,这样机器就能识别了。

我们一般说的编码,指字符集和字符集中字符对应的值,而不是编码方式

一般的编码方式就是字符集中字符的编码对应的二进制

Unicode字符集有所不同,有多种常用的编码方式,后面详细介绍。

1.4 为什么有不同的编码

因为有不同的人,使用了不同的字符,如中国人使用了中文字符,正如你现在读到的字符一样,我们在ASCII码表中是找不到对应字符的,自然也不能根据ASCII把字符转换为字节。

所以我们自己就需要新的编码方式,来包含我们自己的字符,从最开始的GB2312到GBK,再到GB18030。

同理,其他语言也有自己对应的字符集和编码。

二、Unicode

在国际化的进程中,只玩自己的编码肯定是不行的,所以需要统一的编码,就是包含世界字符,这样只需要在应用中使用Unicode码,就能避免为应用做适配。

我们知道编码是包含字符集和字符集中字符对应的值,一般字符集和对应的值放在一起做为一个对应编码的码表。

Unicode有2个字符集,UCS-2和UCS-4,因为UCS-4兼容UCS-2,所以这里介绍一下UCS-4。

UCS-4使用4字节编码,最高位都是0,所以最高字节的8位有7位有效位。

UCS-4使用最高字节的7位来标识组(group),也就是有2^7=128个组。

UCS-4使用次高字节来标识平面(plane),8位共2^8=256个平面。

UCS-4使用第3字节来标识行(row),8位2^8=256行。

UCS-4使用第4字节来标识码位(cell),同理有256个码位。

group 0的plane 0被称作Basic Multilingual Plane(BMP)

UCS-4中,高两个字节为0的码位被称作BMP,BMP去掉2个全为0的高字节之后,就是2字节的UCS-2。

通过上面的介绍我们很容易算出:UCS-2共有2^16=65536个码位。

这也是在python2.x中:

import sys
print sys.maxunicode

打印的值是65535的原因(从0开始,所以最大码位是65535)。

python2-unicode

Unicode计划使用了17个平面,每个平面65536个码位,共有17×65536=1114112个码位。

这也是在python3.x中:

import sys
print sys.maxunicode

打印的值是1114111的原因

python3-unicode

三、python乱码的起源

先介绍一下'中文'这2个字符的相关编码:

'中文'的Unicode编码:\u4e2d\u6587 '中文'的UTF-8编码:\xe4\xb8\xad\xe6\x96\x87 '中文'的GBK编码:\xd6\xd0\xce\xc4

下面代码不是通过交互程序,而是写入文件,在cmd执行。

print("中文")

加入有文件名为test-encoding.py的文件内容如上所示,我们在中文环境windows下执行该文件,就会得到下面的错误。

Non-ASCII错误

test-encoding文件编码

我们可以通过查看test-encoding.py文件的16进制,可以看出test-encoding.py文件使用的是UTF-8编码,对比'中文'的编码得出。

我们也可以看出这不是一个执行时候的错误,因为这是一个SyntaxError,说明是一个语法错误。

同时这个错误也告诉了我们原因,是因为有非ASCII字符,因为python默认的编码是ASCII,所以不能包含有非ASCII字符。

需要包含非ASCII字符我们需要一点特殊的处理,就是告诉python解释器,文件使用的编码方式。

# -*- coding: utf-8 -*-
print("中文")

添加的注释部分是告诉python解释器使用utf-8方式做为字符与字节之间的编码方式。

告诉解释器使用utf-8编码就够了吗?

显然不是,因为还涉及到其他的编码方式,例如我们在cmd中执行上面的文件,会得到输出

涓枃

中文UTF-8编码用GBK解码

上面的输出值其实就是'中文'这2个字符的UTF-8编码\xe4\xb8\xad\xe6\x96\x87对应的GBK编码。 可以自己对照GBK码表检查,也可以使用在线转换编码工具转换验证。

因为windows默认的代码页是936,也就是GBK编码。

windows默认代码页

可以通过下面的2中方式解决:

# -*- coding: utf-8 -*-
print(u"中文")
print(a.decode('utf-8').encode('gbk'))

四、令人困惑的默认编码

python2.x默认的编码是ASCII python3.x默认的编码是UTF-8

可以通过sys.getdefaultencoding()查看默认编码方式

那么默认的编码方式到底是什么?

在有些风骚的操作中,可能会教你使用下面的方式解决乱码:

# -*- coding: utf-8 -*-
import sys

print(sys.getdefaultencoding())

reload(sys)
sys.setdefaultencoding('utf-8')

print(sys.getdefaultencoding())

a = '中文'
print(a)

然而这并没有什么用,因为这是输出端的问题

那这个骚操作在什么地方有用呢?

答案是处理unicode的时候:

# -*- coding: utf-8 -*-
import sys

print(sys.getdefaultencoding())

# reload(sys)
# sys.setdefaultencoding('utf-8')

with open(r'F:\tmp\test.txt', 'w') as f:
  f.write('测试')
  f.write(u'测试')

执行上面的代码,会得到下面的错误(2.x):

UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)

UEE

但是把注释代码取消注释,就没有问题了。

我们来分析一下: f.write(str)接收的是一个str类型的参数,u'测试'是一个unicode类型。

unicode到str需要encode,字节和字符之间的转换是需要编码的,显然这里使用的是:

u'测试'.encode(sys.getdefaultencoding())

五、python 交互式编码

python 交互式使用的编码默认会读取系统的编码,中文环境下windows下一般是GBK。

所以我们使用交互式的方式一般是没有乱码的问题。

六、python处理网页文件编码

UnicodeEncodeError: 'gbk' codec can't encode character u'\xa0' in position 392477: illegal multibyte sequence

unicodeencodeError

有时候会遇到上面的错误,出现这个错误的原因基本都是因为有输出到cmd的命令,也就是基本是因为print('xxx')造成的,其中xxx是GBK不兼容的字符。

其实我们可以很容易的构造一个类似的错误出来(2.x):

print('龦'.decode("utf-8"))
#print('龦'.decode("utf-8").encode("gbk"))

在中文环境下的windows命令行执行包含上面代码的文件就会看到UnicodeEncodeError错误。

Unicode编码中: 基本汉字范围:4E00-9FA5(20902个) 基本汉字补充:9FA6-9FEF(74个) 龦的Unicode码是:9FA6,GBK中是没有包含龦这个字的

解决办法: 出现这个错误,就说明包含有超出GBK范围的字符了,所以就不要使用GBK编码方式了。

中文windows命令行默认GBK,不修改代码页肯定有这个问题,只有修改代码页才能解决。

换种思路直接输出到文件,使用UTF-8编码不是挺好的。

七、总结

python 2.x

python2-编码

#a是字符串(str)类型
a = '中文'
#b是unicode类型
b = u'中文'

2者的不同之处在于:

  1. str类型使用的是系统默认编码(可设置)
  2. unicode的类型使用的是unicode的编码

转换: str通过decode可以获取到unicode类型 unicode的通过encode得到str类型

2.x默认使用的是ASCII做字符与字节之间的编码转换,使用的字符集是UCS-2。

因为2.x没有byte类型,所以不同编码之间要转换先存储为unicode编码,然后需要什么编码,再从unicode编码转换为对应的编码。

python 3.x

python3-编码

相比于2.x,3.x版本就正常多了,和其他语言的逻辑保持了一致性。

首先最重要的是添加了byte类型,即字节类型。

不再显示的使用unicode编码,而是遵从:

字符<--->编码<--->字节
#字符--->编码--->字节
bs = "中文".encode('utf-8')

#字节--->编码--->字符
ch = bs.decode("utf-8")

3.x默认使用utf-8做字节和字符之间转换的编码方式,使用的字符集是UCS-4。

八、解决乱码终极方案

乱码说明字节-->字符的编码出问题了。

所以首先得弄清楚文件、网络中的字节使用的是什么编码方式,是ASCII、GBK、UTF-8……

然后得弄清楚输出字符的终端、编辑器是什么编码,然后对应起来: decode、encode配对使用一样的编码就可以了。

有些朋友可能会说,这不是废话吗?我知道是什么编码,还会乱码?

问题的重点在于:字节从哪里来,显示字符的编辑器或终端使用什么编码。这样至少可以定位大多数问题。

至于中间经过多次转换的,那就需要仔细排查了。

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部