彻底搞懂python中⽂乱码问题
前⾔
曾⼏何时 Python 中⽂乱码的问题困扰了我很多很多年,每次出现中⽂乱码都要去⽹上搜索答案,虽然解决了当时遇到的问题但下次出现乱码的时候⼜会懵逼,究其原因还是知其然不知其所以然。现在有的⼩伙伴为了躲避中⽂乱码的问题甚⾄代码中不使⽤中⽂,注释和提⽰都⽤英⽂,我曾经也这样⼲过,但这并不是解决问题,⽽是逃避问题,今天我们⼀起彻底解决 Python 中⽂乱码的问题。
基础知识
ASCII
很久很久以前,有⼀⼈,他们决定⽤8个可以开合的晶体管来组合成不同的状态,以表⽰世界上的万物。他们看到8个开关状态是好的,于是他们把这称为”字节“。再后来,他们⼜做了⼀些可以处理这些字节的机器,机器开动了,可以⽤字节来组合出很多状态,状态开始变来变去。他们看到这样是好的,于是它们就这机器称为”计算机“。开始计算机只在美国⽤。⼋位的字节⼀共可以组合出256(2的8次⽅)种不同的状态。 他们把其中的编号从0开始的32种状态分别规定了特殊的⽤途,⼀但终端、打印机遇上约定好的这些字节被传过来时,就要做⼀些约定的动作。遇上0×10, 终端就换⾏,遇上0×07, 终端就向⼈们
嘟嘟叫,例好遇上0x1b, 打印机就打印反⽩的字,或者终端就⽤彩⾊显⽰字母。他们看到这样很好,于是就把这些0×20以下的字节状态称为”控制码”。他们⼜把所有的空 格、标点符号、数字、⼤⼩写字母分别⽤连续的字节状态表⽰,⼀直编到了第127号,这样计算机就可以⽤不同字节来存储英语的⽂字了。⼤家看到这样,都感觉很好,于是⼤家都把这个⽅案叫做 ANSI 的”Ascii”编码(American Standard Code for Information Interchange,美国信息互换标准代码)。当时世界上所有的计算机都⽤同样的ASCII⽅案来保存英⽂⽂字。
GB2312
后来,就像建造巴⽐伦塔⼀样,世界各地的都开始使⽤计算机,但是很多国家⽤的不是英⽂,他们的字母⾥有许多是ASCII⾥没有的,为了可以在计算机保存他们的⽂字,他们决定采⽤ 127号之后的空位来表⽰这些新的字母、符号,还加⼊了很多画表格时需要⽤下到的横线、竖线、交叉等形状,⼀直把序号编到了最后⼀个状态255。从128 到255这⼀页的字符集被称”扩展字符集“。从此之后,贪婪的⼈类再没有新的状态可以⽤了,美帝国主义可能没有想到还有第三世界国家的⼈们也希望可以⽤到计算机吧!等中国⼈们得到计算机时,已经没有可以利⽤的字节状态来表⽰汉字,况且有6000多个常⽤汉字需要保存呢。
但是这难不倒智慧的中国⼈民,我们不客⽓地把那些127号之后的奇异符号们直接取消掉, 规定:⼀个
⼩于127的字符的意义与原来相同,但两个⼤于127的字符连在⼀起时,就表⽰⼀个汉字,前⾯的⼀个字节(他称之为⾼字节)从0xA1⽤到 0xF7,后⾯⼀个字节(低字节)从0xA1到0xFE,这样我们就可以组合出⼤约7000多个简体汉字了。在这些编码⾥,我们还把数学符号、罗马希腊的字母、⽇⽂的假名们都编进去了,连在 ASCII ⾥本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的”全⾓”字符,⽽原来在127号以下的那些就叫”半⾓”字符了。 中国⼈民看到这样很不错,于是就把这种汉字⽅案叫做 “GB2312“。GB2312 是对 ASCII 的中⽂扩展。
GBK
但是中国的汉字太多了,我们很快就就发现有许多⼈的⼈名没有办法在这⾥打出来,特别是某些很会⿇烦别⼈的国家领导⼈。于是我们不得不继续把 GB2312 没有⽤到的码位出来⽼实不客⽓地⽤上。 后来还是不够⽤,于是⼲脆不再要求低字节⼀定是127号之后的内码,只要第⼀个字节是⼤于127就固定表⽰这是⼀个汉字的开始,不管后⾯跟的是不是扩展字符集⾥的内容。结果扩展之后的编码⽅案被称为 GBK 标准,GBK 包括了 GB2312 的所有内容,同时⼜增加了近20000个新的汉字(包括繁体字)和符号。 后来少数民族也要⽤电脑了,于是我们再扩展,⼜加了⼏千个新的少数民族的字,GBK 扩成了 GB18030。
从此之后,中华民族的⽂化就可以在计算机时代中传承了。 中国的程序员们看到这⼀系列汉字编码的
标准是好的,于是通称他们叫做“DBCS“(Double Byte Charecter Set 双字节字符集)。在DBCS系列标准⾥,最⼤的特点是两字节长的汉字字符和⼀字节长的英⽂字符并存于同⼀套编码⽅案⾥,因此他们写的程序为了⽀持中处理,必须要注意字串⾥的每⼀个字节的值,如果这个值是⼤于127的,那么就认为⼀个双字节字符集⾥的字符出现了。那时候凡是受过加持,会编程的计算机僧侣们都要每天念下⾯这个咒语数百遍: “⼀个汉字算两个英⽂字符!⼀个汉字算两个英⽂字符……”
因为当时各个国家都像中国这样搞出⼀套⾃⼰的编码标准,结果互相之间谁也不懂谁的编码,谁也不⽀持别⼈的编码,连⼤陆和台湾这样只相隔了150海⾥,使⽤着同⼀种语⾔的兄弟地区,也分别采⽤了不同的 DBCS 编码⽅案——当时的中国⼈想让电脑显⽰汉字,就必须装上⼀个”汉字系统”,专门⽤来处理汉字的显⽰、输⼊的问题,但是那个台湾的愚昧封建⼈⼠写的算命程序就必须加装另⼀套⽀持 BIG5 编码的什么”倚天汉字系统”才可以⽤,装错了字符系统,显⽰就会乱了套!这怎么办?⽽且世界民族之林中还有那些⼀时⽤不上电脑的穷苦⼈民,他们的⽂字⼜怎么办? 真是计算机的巴⽐伦塔命题啊!
UNICODE
正在这时,⼤天使加百列及时出现了,⼀个叫 ISO(国际标谁化组织)的国际组织决定着⼿解决这个问题。他们采⽤的⽅法很简单:废了所有的地区性编码⽅案,重新搞⼀个包括了地球上所有⽂化、所
有字母和符号的编码!他们打算叫它”Universal Multiple-Octet Coded Character Set”,简称 UCS, 俗称 “unicode“。unicode开始制订时,计算机的存储器容量极⼤地发展了,空间再也不成为问题了。于是 ISO 就直接规定必须⽤两个字节,也就是16位来统⼀表⽰所有的字符,对于ASCII⾥的那些“半⾓”字符,unicode 包持其原编码不变,只是将其长度由原来的8位扩展为16位,⽽其他⽂化和语⾔的字符则全部重新统⼀编码。
由于”半⾓”英⽂符号只需要⽤到低8位,所以其⾼8位永远是0,因此这种⼤⽓的⽅案在保存英⽂⽂本时会多浪费⼀倍的空间。这时候,从旧社会⾥⾛过来的程序员开始发现⼀个奇怪的现象:他们的 strlen 函数靠不住了,⼀个汉字不再是相当于两个字符了,⽽是⼀个!是的,从 unicode 开始,⽆论是半⾓的英⽂字母,还是全⾓的汉字,它们都是统⼀的”⼀个字符“!同时,也都是统⼀的”两个字节“,请注意”字符”和”字节”两个术语的不同,“字节”是⼀个8位的物理存贮单元,⽽“字符”则是⼀个⽂化相关的符号。在 unicode 中,⼀个字符就是两个字节。⼀个汉字算两个英⽂字符的时代已经快过去了。
unicode 同样也不完美,这⾥就有两个的问题,⼀个是,如何才能区别 unicode 和 ASCII?计算机怎么知道三个字节表⽰⼀个符号,⽽不是分别表⽰三个符号呢?第⼆个问题是,我们已经知道,英⽂字母只⽤⼀个字节表⽰就够了,如果 unicode 统⼀规定,每个符号⽤三个或四个字节表⽰,那么每个英⽂字母前都必然有⼆到三个字节是0,这对于存储空间来说是极⼤的浪费,⽂本⽂件的⼤⼩会因此⼤出⼆三倍,这是难以接受的。
UTF-8
unicode 在很长⼀段时间内⽆法推⼴,直到互联⽹的出现,为解决 unicode 如何在⽹络上传输的问题,于是⾯向传输的众多 UTF(UCS Transfer Format)标准出现了,顾名思义,UTF-8就是每次8个位传输数据,⽽ UTF-16 就是每次16个位。UTF-8就是在互联⽹上使⽤最⼴的⼀种 unicode 的实现⽅式,这是为传输⽽设计的编码,并使编码⽆国界,这样就可以显⽰全世界上所有⽂化的字符了。UTF-8 最⼤的⼀个特点,就是它是⼀种变长的编码⽅式。它可以使⽤1~4个字节表⽰⼀个符号,根据不同的符号⽽变化字节长度,当字符在 ASCII 码的范围时,就⽤⼀个字节表⽰,保留了 ASCII 字符⼀个字节的编码做为它的⼀部分,注意的是 unicode ⼀个中⽂字符占2个字节,⽽UTF-8⼀个中⽂字符占3个字节)。从 unicode 到 uft-8 并不是直接的对应,⽽是要过⼀些算法和规则来转换。
看到这⾥你是彻底懵逼还是恍然⼤悟,如果是彻底懵逼建议你再多看⼏次,温故⽽知新,如果恍然⼤悟的话我们就接着往下看。
中⽂乱码实例讲解介绍完了基础知识,我们来说说 Python 中是如何存储字符的,先来看⼀个乱码的例⼦。新建⼀个 demo.py ⽂件,⽂件存储格式为utf-8⽂件中内容如下。在 cmd 中运⾏ python demo.py ,什么,我只是想打印中⽂
两个字居然给我报错,简直不可理喻啊!
赶紧打开 python ⾃带的 idle 试试看,⼀点问题都没有啊,这是为什么呢?回头好好看看 cmd 下报的错误Non-ASCII character '\xe4' in file demo.py on line 1, but no encoding declared;,翻译过来就是 在 demo.py ⽂件的第 1 ⾏有⾮ ASCII 字符 ‘\xe4’,⽽且没有声明编码,从上⾯基础知识可知,ASCII 编码是不能表⽰汉字中⽂的,demo.py ⽂件第⼀⾏有中⽂两个汉字,⽽ demo.py ⽂件存储格式为utf-8,所以中⽂两个汉字在⽂件中存储的时候是以 utf-8编码存储的,查看 demo.py ⽂件 16 进制可以看到中⽂ 存储的是 \xe4\xb8\xad\xe6\x96\x87。s = "中⽂"print s
1
2
16 进制查看⽤的是 notepad++ ⾃带的 HEX-Editor 插件,另外函数 repr
也能显⽰原始字符串,如下。defaultencoding()读取 python 默认编码是 ASCII,⽽ ASCII 是不认识 \xe4的,所以会报错Non-ASCII character '\xe4' in file demo.py on line 1, but no encoding declared;,此时只要在 demo.py ⽂件头加上 # encoding:utf-8就可以了,虽然是注释,但 python 看到这句话就知道
了接下来应该⽤
utf-8编码了,⽽ demo.py 存储时也是utf-8的,所以就正常了。
编码声明注释写成# -*- coding: utf-8 -*-也是可以的,只要满⾜正则表达式^[ \t\v]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+)就OK。
我们再次在 cmd 下运⾏ python demo.py 试试看。
啥,啥,啥,说好的显⽰中⽂呢?这不是逗我吗?去 python idle 下试试看。
为什么同样的⽂件在 python idle 中却正常呢?肯定是 cmd 有问题,是的,我也是这样想的,那我试着在 cmd 下进⼊ python 交互模式输出中⽂看看,我去居然 cmd 下也是可以正常输出 中⽂的,相信看到这⾥⼩伙伴们都已经晕了。
# encoding:utf-8import sys print defaultencoding()s = "中⽂"print repr(s)1
2
3
4
5# encoding:utf-8s = "中⽂"print s
1
2
3
别急,听我慢慢分析。其实当在 cmd 或者 idle 中打印字符的时候已经和⽂件编码⽅式没有关系了,此时起作⽤的是输出环境也就是 cmd 或者 idle 的编码⽅式有关,查看 cmd 的编码命令是 chcp ,返回 936,去⽹上查可知 936 代表 GBK 编码,这下我们⼤概知道什么原因了,demo.py ⽂件存储和编码声明都是utf-8,但是 cmd 显⽰编码是 GBK ,⽽将中⽂的utf-8 编码 \xe4\xb8\xad\xe6\x96\x87 强制转换为 GBK 就会乱码了,GBK 是两个字节存储⼀个中⽂字符,所以 \xe4\xb8\xad\xe6\x96\x87 会解码成三个字,很不幸这三个字涓 枃不是常⽤字也不是我们想要的字符,所以就认为是乱码了。为什么在 cmd 下进⼊ Python 交互式命令⾏可以呢,这是因为当在 python 交互式命令⾏输⼊s = "中⽂"时,中⽂这两个汉字其实是以 GBK 编码存储的,cmd 默认编码是 GBK ,不信看s
打印\xd6\xd0\xce\xc4,这就是GBK 编码⽅式存储,⽽utf-8编码⽅式存储同样的中⽂
为\xe4\xb8\xad\xe6\x96\x87。下⾯告诉⼤家怎么解决在 cmd 下执⾏⽂件正确输出中⽂问题。
1、demo.py ⽂件和编码声明都为 GBKpython怎么读取py文件
这种⽅法⽐较笨,就是把 demo.py ⽂件改为 GBK 存储,⽽且编码声明也是GBK ,个⼈不推荐。
2、中⽂⽤ unicode 表⽰
只要在中⽂前⾯加上个⼩u 标记,后⾯的中⽂就⽤ unicode 存储了。cmd 下是可以打印 unicode 字符的,如下。
3、把中⽂强制转换为GBK 或者unicode 编码
强制转换为unicode 编码,在 Python 中编码是可以互相转换的,⽐如从utf-8转换为gbk ,不同编码之间不能直接转换,需要通
过unicode 字符集中间过渡下,从上⾯基础知识可知unicode 是⼀种字符集,不属于编码,⽽utf-8是具体实现unicode 思想的⼀种编码。utf-8转换为unicode 是⼀种解码过程,通过decode 可从utf-8解码成unicode 。
# encoding:gbk s = "中⽂"print s print repr(s)
1
2
3
4# encoding:utf-8s = u"中⽂"print s print repr(s)
1
2
3
4
强制转换为gbk 编码,上⼀步已经从utf-8转换为unicode 了,从unicode 是编码的过程,通过encode
实现。
总结 windows cmd 窗⼝下不⽀持utf-8,想要显⽰中⽂必须转换为gbk或者unicode,⽽ Python idle 中这三种编码都⽀持。中⽂乱码的出现都是由于编码不⼀致导致的,存储的是⽤utf-8,打印的时候⽤gbk就会乱码了,所有要保证不乱码尽量保持统⼀,建议全部使⽤unicode。
decode 解码从其它编码变成unicode
叫解码,解码⽤的⽅法是decode ,第⼀个参数为被解码的字符串原始编码格式,如果写错了也会报错。⽐如 s 是utf-8,⽤gbk 去解码就会报错。
⼩提⽰ 在 Python idle 和 cmd 下直接输⼊ s = "中⽂"会以 gbk 编码的,如果在⽂件中输⼊ s = "中⽂"且⽂件存储格式为utf-8,那么 s 是以utf-8编码存储的,有点不⼀样曾经踩过坑,及时 Python idle 成功了⽂件运⾏的时候也可能失败。
encode 编码# encoding:utf-8s = "中⽂"u = s.decode('utf-8')print u print type(u)print repr(u)
1
2
3
4
5
6# encoding:utf-8s = "中⽂"u = s.decode('utf-8')g = u.encode('gbk')print g print type(g)print repr(g)
1
2
3
45
6
7# encoding:utf-8s = "中⽂"u = s.decode('gbk')print u print repr(u)
1
2
3
4
5
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论