为啥会出现乱码-python

尝试编写批量处理文件的短脚本后,才发现被莫名其妙出现的乱码以及莫名其妙的读文件报错等等问题整的不厌其烦,然后就找到了篇能够给我解惑的文章,该原理足以解释我目前已遇到的乱码问题。

参考文章链接借用参考文献中的代码实例,加入了一些我自己的理解

乱码的产生

1.编码方式的演变

1
2
3
4
5
6
7
8
9
10
11
ASCII————不兼容——Unicode:定长编码,2字节表示1字符
| /
ISO8859-1 /
/ \ /
/ \ /
GB2312 UTF-8:变长编码,1-4字节(英文1字节,中文3字节)
|
GBK(汉字内码扩展规范)
|
GB18030
兼容ISO8859-1,英文1字节,汉字2字节

2.乱码的产生原因

在单一编码或兼容编码方式下,乱码当然不会存在。但是当编码和解码采用两种方式的时候,乱码就如约而至了

比如文本文件在保存的时候,内容会以GBK的编码方式保存到硬盘;在打开的时候同样会以GBK的解码方式从硬盘读取,这时候你用utf-8的解码方式读出来不肯定乱码。

1
2
3
4
5
6
7
#123.txt的内容为小明
with open("C:/Users/Administrator/Desktop/123.txt", "r+",encoding = "utf-8") as f1:
b = f1.read()
# 输出结果为:
File "F:\python\lib\codecs.py", line 322, in decode
(result, consumed) = self._buffer_decode(data, self.errors, final)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc3 in position 2: invalid continuation byte

就是因为读的时候,就用utf-8对编码进行解码,解不开gbk的码,直接报错。

1
2
3
4
5
a = "小明"
with open("C:/Users/Administrator/Desktop/123.txt", "r+",encoding="utf-8") as f1:
f1.write(a)
#txt文件结果:
灏忔槑

这次就是我用utf-8的编码后的二进制(其实是中间码,这里为方便理解,写成二进制码)直接写到硬盘中,打开的时候通过gbk解码打开,输出乱码

这就相当于我拿一个密码册1把中文翻译成了加密电报,但是解密的时候却用的是完全不搭噶的密码册2,这翻译出来的还是原来的文件?

实例一

注意:这里是在linux下的运行结果,操作系统默认的编解码方式是utf-8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import sys, locale
s = "小甲"
print(s)
print(type(s))
print(sys.getdefaultencoding())
print(locale.getdefaultlocale())
with open("utf1","w",encoding = "utf-8") as f:
f.write(s)
with open("gbk1","w",encoding = "gbk") as f:
f.write(s)
with open("jis1","w",encoding = "shift-jis") as f: #(shift-jis是日文编码格式)
f.write(s)
# 运行结果
小甲
<class 'str'>
utf-8
('en_US', 'UTF-8')
# 三个文件中写入的内容
utf1 : 小甲
gbk1 : С▒▒▒
jis1 : ▒▒▒b

两个“utf-8”是什么意思?

上面的 utf-8 指:系统默认编码

  • 注: 不要把系统以为是操作系统,这里可以理解成python3的编译器本身

下面的 utf-8 指:本地默认编码

  • 注: 这个才是操作系统的编码。(在Windows运行会变成gbk)

为什么gbk1 、jis1 的内容出现了乱码?

我在打开文件时,linux系统用utf-8进行解码

那么写入磁盘时不是用utf-8, 读出时却用utf-8,当然读不出来了。

这里的encoding,就相当于将二进制文件以utf-8的形式进行编/解码,所以不会报错,甚至可以直接省略这一参数(python默认)

进阶

系统默认编码 指:
在python3编译器读取.py文件时,若没有头文件编码声明,则默认使用“utf-8”来对.py文件进行解码。并且在调用 encode()这个函数时,不传参的话默认是“ utf-8 ”。(这与下面的open( )函数中的“encoding”参数要做区分,非常误导人!!!

本地默认编码 指:
在你编写的python3程序时,若使用了 open( )函数 ,而不给它传入 “ encoding ” 这个参数,那么会自动使用本地默认编码。没错,如果在Windows系统中,就是默认用gbk格式!!!

1.# coding=的意义

例如代码首行定义的coding =utf-8,它的意思是python3编译器在读取该.py文件时候,我应该用什么格式将它 “解码”

py代码总归是以二进制形式保存在硬盘上的,其编码方式肯定是gbk

但是python文件在执行的时候系统却是默认编码utf-8的编解码方式,也就是对我的gbk文件重新编码,最后以utf-8的二进制码在内存和cpu间调用。

所以首行的coding = utf-8也就没啥添加的意义了。

2.encoding参数的意义

这里我们新建md文件,并通过软件typora编辑器打开

1
2
3
4
5
a = "小明"
with open("C:/Users/Administrator/Desktop/123.md", "r+") as f1:
f1.write(a)
#结果:
小明槑

现在用在md文件上,发现不加encoding会报错,和记事本刚好反过来?

这就说明了typora不像是系统自带的记事本那样,它有自己的独立编解码方式。typora的编解码方式默认为utf-8,所以需要在写入时对内容进行重新编码,encoding = utf-8

1
2
3
4
5
6
7
8
a = "小明
with open("C:/Users/Administrator/Desktop/123.md", "r+",encoding="gbk") as f1:
f1.write(a)
with open("C:/Users/Administrator/Desktop/123.md", "r+", encoding="gbk") as f2:
b = f2.read()
print(b)
# 输出结果
小明

不是说gbk不能被typora正确读取吗,为啥还能输出结果?

其实,typora还真读取不了,打开的时候直接保存,取消写入操作

但是我们的python语言是实打实的把gbk编码写入了文件指向的硬盘空间啊,这样我们再用gbk对这块代码进行解码不是没毛病吗。

当然我们windows下,默认的encoding就是gbk,这里直接省略的话就是这种情况。

总结

现在可以清楚地认识到

进了cpu,怎么编码和解码都是python解释器说了算,也就是系统默认编码或者首行定义编码

但是对于文件操作而言(open),就都是本地编码,比如以记事本为首的系统软件,编解码方式就是gbk,而像typora这样的软件也有自己的想法(utf-8)。在进行文件操作的时候就只需要考虑硬盘就可以了。

执行写操作就是以encoding的参数进行编码,写入硬盘

执行读操作就是以encoding的参数从硬盘读取,解码,类似于打开编辑器的操作

问题:

  • 为什么gkb解码utf-8的文件是显示乱码, 而 utf-8解码gkb的文件却是报错呢??

解释:

  • 这是因为 utf-8 与 gbk 编码的算法差异。
  • 我们最常看到的是utf-8解码报错,因为它是可变长的的编码,有1个字节的英文字符,也有2个字节的阿拉伯文,也有3个字节的中文和日文。
  • gbk是定长的2字节,比较死板,utf-8编出来的二进制文件,它常常也会一股脑的全部按照2个字节、2个字节地去解码,结果可想而知,全是乱码!!!
  • 而utf-8是有严格定义的,一个字节的字符高位必须是0;三个字节的字符中,第一个字节的高位是1110开头