Python进阶-编解码问题全解

image.png

默认讨论的都是Python3.6。

1. 计算机编码的发展历程

1.1 编码的诞生

计算机的存储与计算都是以二进制的形式进行的;因此对于逻辑符号Char(数字,字母,中文,数学字符,其他字符如制表符)需要有对应的二进制码表示,这就是编码的作用。

编码有两个方面,一个是字符集,一个是字符集对应的编码规则/算法。一般来说,人们认为Unicode编码是一种字符集,utf-8与utf-16是具体的字符编码规则/算法。

但是如果要把unicode看做编码规则也是可以的,有n个字符就用十进制n的二进制形式来表示这个字符集即可,最早的ascii码就是这样做的。但是这样很难实现unicode作为万国码需要满足的一些特性:1.向下兼容,2.易于拓展,3.兼顾存储与传输性能。

1.2 ASCII

ASCII(American Standard Code for Information Interchange,美国标准信息交换代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是现今最通用的单字节编码系统,并等同于国际标准ISO/IEC 646。

标准 ASCII 码使用 7 个二进位对字符进行编码。基本的 ASCII 字符集共有 128 个字符,其中有 96 个可打印字符,包括常用的字母、数字、标点符号等,另外还有 32 个控制字符。0~31表示控制字符如回车、退格、删除等;32~126表示打印字符即可以通过键盘输入并且能显示出来的字符。
image.png!
在ASCII中,字符集(charset)编码与编码encoding方案等价。而UNICODE中字符集(charset)编码与编码encoding方案不等价。

1.2.1 ISO-8859-1(latin1)

ASCII只能表示128个字符,显示是不能完全表示完的,所以ISO-8859-1(latin-1)扩展了ASCII编码,在ASCII编码之上又增加了西欧语言、希腊语、泰语、阿拉伯语、希伯来语对应的文字符号,它是向下兼容ASCII编码的。

1.3 ANSI

ASCII是由美国国家标准学会(American National Standard Institute , ANSI )制定的,是一种标准的单字节字符编码方案,用于基于文本的数据;ascii是ansi标准,包含128个字符(7 bits)。

但是,我们常说的ANSI编码,通常特指windows平台的一种基于ANSI标准的ASCII扩展码,他将ASCII码扩展到8bits,增加了0x80-0xff共128个字符。ANSI码仅在前126个与ASCII码相同。

  • 在简体中文Windows操作系统中,ANSI 编码代表 GBK 编码
  • 在英文Windows操作系统中,ANSI 编码代表 ASCII编码
  • 在繁体中文Windows操作系统中,ANSI编码代表Big5
  • 在日文Windows操作系统中,ANSI 编码代表 Shift_JIS 编码

1.3.1 GB2312>GBK>GB18030

GBK,全称为Chinese Internal Code Specification,即汉字内码扩展规范,于1995年制定[gb2312为1980年]。它主要是扩展了GB2312,在它的基础上又加了更多的汉字,它一共收录了21003个汉字。GBK是向下兼容GB2312编码的,也就是说GB2312编码的汉字可以用GBK正常解码不会出现乱码,但用GBK编码的汉字用GB2312解码就不一定了。

GB18030全称汉字内码扩展规范,是现在最新的内码字集于2000年发布,并于2001年强制执行,包含了中国大部分少数民族的语言字符,收录汉字数超过70000余个。

它主要采用单字节、双字节、四字节对字符编码,它是向下兼容GB2312和GBK的,虽然是我国的强制使用标准,但在实际生产中很少用到,用得最多的反而是GBK和GB2312。

1.4 Unicode

为了自己的语言能在计算机中正常显示,每个国家和地区都有各自的编码,所以编码多了谁也不认识对方的编码,这时候ISO组织就提出了一种新的编码叫UNICODE编码让全球的文化、字符、符号都能支持。UNICODE在制定时计算机容量已不是问题,所以设计成了固定两个字节,所有的字符都用16位表示,包括之前只占8位的英文字符等,所以会造成空间的浪费,UNICODE在很长的一段时间内都没有得到推广应用。

UNICODE可以理解为一种字符集映射方案;基于该方案也可以直接进行二进制编码(例如utf-16实现),但是效率会比较低,因此又产生了utf-8实现。

1.4.1 UTF-16

UTF-16是UNICODE的具体实现,16即16位,UTF-16即是这个来由,定义了UNICODE字符在计算机中的存储方式,UTF-16同样使用了两个字节来表示任何字符,这样使得操作字符串非常高效,这也是java把UTF-16作为字符在内存中存储的格式的重要原因。

1.4.2 UTF-8

虽然UTF-16很高效,但也是UNICODE最大的坏处,使得所有单字节字符一定要占两个字节,存储空间放大了一倍,这明显消耗了资源,不符合现在互联网高速发展的现状。所以有了UTF-8,它是UNICODE的一种可变长度字符编码的实现。

  • 它可以使用1~6个定长字节来编码UNICODE字符。
  • UTF-8对ASCII字符使用单字节存储,单个字符损坏也不会影响后面的字符,所以UTF-8非常适合在网络上面传输,也是现在使用最广泛的编码之一。
  • 如果要表示中文,UTF-8编码效率要大于GBK,小于UTF-16,所以它也是除了GBK之外最理想的编码方式。
  • UTF-16适合在磁盘与内存之间使用,字符和字节的相互转换会更加简单和高效,但不适合在网络上传输,因为网络传输可能会损坏字节流。

2. Python3编解码体系

2.1 bytes和str

Python3严格区分了 str 和 bytes 两种类型。Python3不会以任意隐式的方式混用 str 和 bytes。因此使用者不能拼接字符串和字节包,也无法在字节包里搜索字符串(反之亦然),也不能将字符串传入参数为字节包的函数(反之亦然)。

>>> client.send("test str") 
Traceback (most recent call last): File "", line 1, in  TypeError: a bytes-like object is required, not 'str' 
>>> client.send(b"test str") #将参数转换成 bytes 类型 8 #返回发送的数据长度 

以文本为例,Python在工作中会将文本数据读取到内存decode为unicode编码,前面说了unicode编码的UTF-16实现由于操作字符串非常高效,因此很适合在内存中使用。在用户的角度,这些decode为unicode编码的字符就是Python中的str。

在内存中完成运算之后,将文本数据写回到磁盘中,需要将数据再encode为utf-8(一般情况下使用utf-8,其他的也是可以的)。在用户的角度,这些encode为utf-8的字符就是Python中的bytes。

>>> b = bytes('中文', 'utf-8') 
>>> b 
b'\\xe4\\xb8\\xad\\xe6\\x96\\x87' 
>>> b.decode('Windows 1252') 
'ä¸\\xadæ–‡' 
>>> b.decode('ISO8859-7') 
'δΈ\\xadζ\\x96\\x87' 
>>> string = b.decode('utf-8') 
>>> string 
'中文' 
>>> b = string.encode('utf-8') 
>>> b 
b'\\xe4\\xb8\\xad\\xe6\\x96\\x87'

2.2 编解码工作流程

我们可以将Python工作区看作一个水池,一个入口负责从硬盘读取内容转为unicode到内存进行运算与处理,一个出口负责将内存中的数据转为utf-8或者其他编码形式保存到硬盘中。
image.png

2.3 Python常见编解码错误及解决办法

2.3.1 Non-ASCII character

一般出现在数据读取中。原因为系统默认编码类型与文件保存时的编码类型不一致。

2.3.1.1 查看系统编码类型:
sys.getdefaultencoding() 
'utf-8'
2.3.1.2 查看文件编码类型:

vim 打开文件,切换到底线命令模式,在最底一行输入如下命令:

:set fileencoding 
fileencoding=utf-16le

除此之外,还有基于file指定,enca指定的方式。

2.3.1.3 实现匹配
  • 一个方向是在Python读取文件的过程中指定与待读取文件一样的编码方式
  • 另一个方向是将待读取文件转化为utf-8。

哪个高效用哪个即可。

2.3.2 UnicodeEncodeError

一般出现在数据输出中,例如print过程中可能出现的:UnicodeEncodeError: 'ascii' codec can't encode character '\\U0001f621' in posit。

2.3.2.1 标准输出报错

标准输出即Python中的print(); print方法调用之后会根据默认的编码方式将要print的str从unicode转化为对应的编码并交接给操作系统,最后在显示屏上输出。

以下指令可以查询默认的标准输出编码方法:

>>> import sys 
>>> sys.stdout.encoding 
'ANSI\_X3.4-1968'

'ANSI_X3.4-1968' 只能支持标准的ascii码字符输出,那么怎样能够修改默认的标准输出编码标准从而支持更多的输出字符呢?

1. 修改环境变量

export PYTHONIOENCODING=utf-8

2. 每次运行脚本时带上

有的时候可能并不想改动环境变量,那么就直接在脚本前指定。

PYTHONIOENCODING=utf-8 python code.py

3. 代码中修改

import codecs 
sys.stdout = codecs.getwriter("utf-8")(sys.stdout.detach()) 
sys.stdout.write("Your content....")

4. 操作系统设定

更改系统默认的语言编码设定,从而默认标注输出为utf-8。最简单的一种处理方式如下,该方式不一定能支持中文,但是范围肯定大于ASCII码。

LC_ALL=C.UTF-8

2.3.2.2 写入报错

有时候往文本文件或者数据库写入也会遇到编码报错(例如用默认的ASCII码去编码中文字符),解决办法一般为指定写入的编码方式。

# 指定将str编码为bytes的标准为utf-8(也是默认的)
with open('test.txt', 'w', encoding='utf8') as f:  

    f.write('something')

# 'wb'模式只能直接写入bytes类型
with open('test.txt', 'wb') as f:  

    f.write('something'.encode('utf-8'))

4. 关联软硬件编解码问题

在实际的开发过程中,只理解Python的编解码模型是不够的,还需要有一个更加宏观的认识,例如Python与操作系统的编解码关系,Python与其他软件组件的编解码关系。

4.1 数据库编解码

数据库一般都有客户端和服务端。例如在A机器上进行项目开发,在B机器上部署数据库,那么A机器上的sqlclient就是客户端,B机器上的sqlserver就是服务端;A机器需要通过网络远程与B机器的数据库进行交互完成数据的读写。

在A机器上,从B机器的sqlserver读写文件都统一用utf-8则不会有什么问题;但是在B机器上如果要本地读取数据则可能需要特别设置读取的编码类型为utf-8,不然可能会出现读出来乱码的问题。

4.2 操作系统LANG与locale

程序运行使用一套语言需要有字符集(数据)和字体(显示),Locale是根据计算机用户所使用的语言,所在国家或者地区,以及当地的文化传统所定义的一个软件运行时的语言环境。

在locale环境中,有一组变量,代表国际化环境中的不同设置;其中有一个叫做LC_CTYPE ,用于字符分类和字符串处理,控制所有字符的处理方式,包括字符编码,字符是单字节还是多字节,如何打印等。是最重要的一个环境变量。如果在开发过程中遇到了编码问题,操作系统本身的语言环境也是需要去斟酌考虑的

更多细节参考
https://www.cnblogs.com/rusking/p/3695993.html

4.3 建议

4.3.1 规范编码

为了确保后台流程编解码的顺畅进行,最好将系统环境编码,文本编辑器,文件编码和数据库编码设置为一样的。

4.3.2 保证代码源文件编码

在Python2中,py文件的默认编码是ASCII,在源代码文件中,如果用到非ASCII字符,需要在文件头部进行编码声明;Python3默认源文件编码为utf-8。

5. REFERENCE

1. 架构师必须掌握的各种编码

https://blog.csdn.net/moakun/article/details/80178590

2. PYTHON编码和解码

https://blog.csdn.net/can0227/article/details/83240705

3. linux查看文件编码格式

https://blog.csdn.net/mayue_web/article/details/89404395

你可能感兴趣的:(Python进阶-编解码问题全解)