Python编码问题及中文解决方案

一.各种编码的由来

为什么会出现多种编码?

相信计算机专业的都知道,所有的数据(文本,音频,视频等等)在计算机内部都是以二进制形式来表示的。而计算机内部为什么采用二进制则是由硬件决定的(计算机采用了具有两种稳定状态的二值电路)。这样,就引出一个问题:
我们人类不适合直接看二进制。因此,需要用一种方法,将二进制转为我们能看懂的东西。编码就应运而生了。

编码发展历史

第一阶段:

在计算机中,所有的数据只可能是0或者1(用高电平和低电平分别表示1和0),那么我们通常看到的字符也就只能用0和1来表示。于是科学家们(这里指的是美国的科学家)就想出一个办法,把一个特定的数字对应一个特定的字母进行存储和传输,比如我需要存储字母a,那么我存入一个数字97(即在计算机中存入二进制(01100001),这个过程叫做编码(encode),而我们在读取数据的时候,当遇到97时,我们就让计算机显示字母a,这个过程叫做解码(decode)。

这里你应该知道:计算机看懂的东西我们看不懂,我们看懂的东西,计算机看不懂。

把计算机看懂的东西(二进制(01100001))变成我们看懂的东西(数字97,也就是a),这个过程叫解码(decode)。
把我们看懂的东西(数字97,也就是a)变成计算机看懂的东西(二进制(01100001)),这个过程叫做编码(encode)。
为了大家在数据传输的时候不至于产生误会,那么我们需要让所有的人都使用数字97来代表字母a,所以需要制定一份标准(即码表),最开始的这个标准叫做ASCII码表。

ASCII码的实现方式: 最早的计算机在设计时采用8个比特(bit)作为一个字节(byte),所以,一个字节能表示的最大的整数就是255(二进制11111111=十进制255)。
由于计算机是美国人发明的,因此,最早只有127个字符被编码到计算机里(即用一个字节的后七位),也就是大小写英文字母、数字和一些符号,这个编码表被称为ASCII编码,比如大写字母A的编码是65,小写字母z的编码是122。

第二阶段:

随着发展,计算机开始普及,当计算机流传到欧洲时,问题再次出现,原本的ASCII编码只能解决美国人的编码问题,无法将欧洲的文字表示出来。于是乎,欧洲人就把ASCII码中没用到的第一位给用了,即:ASCII码用一个字节的后七位,表示范围是0-127;
欧洲人把这个字节的第一位也用了,表示范围0-255。除去原本的0-127,剩下128-255.128-159之间为控制字符,160-255位文字符号,其中包括了西欧语言、希腊语、泰语、阿拉伯语、希伯来语。砖家们决定把他们的编码名称叫做Latin1,后面由于欧洲统一制定ISO标准,所以又有了一个ISO的名称,即ISO-8859-1。

第三阶段:

计算机技术当然也传到了亚洲大地,比如中国。原本的一个字节的8个位全都用完了,但是要处理中文显然一个字节是不够的,至少需要两个字节,而且还不能和ASCII编码冲突,所以,中国制定了GB2312编码,用来把中文编进去。

但是中国的汉字太多了,我们很快就就发现有许多人的人名没有办法在这里打出来,特别是某些很会麻烦别人的国家领导人。于是我们不得不继续把 GB2312 没有用到的码位找出来老实不客气地用上。 后来还是不够用,于是干脆不再要求低字节一定是127号之后的内码,只要第一个字节是大于127就固定表示这是一个汉字的开始,不管后面跟的是不是扩展字符集里的内容。结果扩展之后的编码方案被称为 GBK 标准,GBK 包括了 GB2312 的所有内容,同时又增加了近20000个新的汉字(包括繁体字)和符号。 后来少数民族也要用电脑了,于是我们再扩展,又加了几千个新的少数民族的字,GBK 扩成了 GB18030。

问题又来了:
你可以想得到的是,全世界有上百种语言,日本把日文编到Shift_JIS里,韩国把韩文编到Euc-kr里,各国有各国的标准,就会不可避免地出现冲突,结果就是,在多语言混合的文本中,显示出来会有乱码。

因此,Unicode应运而生。Unicode把所有语言都统一到一套编码里,这样就不会再有乱码问题了。
到了这里:已经知道的编码方式主要有两种:ASCII和Unicode

现在,捋一捋ASCII编码和Unicode编码的区别:ASCII编码是1个字节,而Unicode编码通常是2个字节。(如果要用到非常偏僻的字符,就需要4个字节)。

字母A用ASCII编码是十进制的65,二进制的01000001;
字符’0’用ASCII编码是十进制的48,二进制的00110000,注意字符’0’和整数0是不同的;
汉字中已经超出了ASCII编码的范围,用Unicode编码是十进制的20013,二进制的01001110 00101101。你可以猜测,如果把ASCII编码的A用Unicode编码,只需要在前面补0就可以,因此,A的Unicode编码是00000000 01000001。

新的问题又出现了:
如果统一成Unicode编码,乱码问题从此消失了。但是,如果你写的文本基本上全部是英文的话,用Unicode编码比ASCII编码需要多一倍的存储空间,在存储和传输上就十分不划算。

所以,本着节约的精神,又出现了把Unicode编码转化为“可变长编码”的UTF-8编码。UTF-8编码把一个Unicode字符根据不同的数字大小编码成1-6个字节,常用的英文字母被编码成1个字节,汉字通常是3个字节,只有很生僻的字符才会被编码成4-6个字节。如果你要传输的文本包含大量英文字符,用UTF-8编码就能节省空间。

在这里插入图片描述
从上面的表格还可以发现,UTF-8编码有一个额外的好处,就是ASCII编码实际上可以被看成是UTF-8编码的一部分,所以,大量只支持ASCII编码的历史遗留软件可以在UTF-8编码下继续工作。

二. ASCII, Unicode, utf-8之间的关系

看完上面的东西,可能有点乱,我这里简单总结一下ASCII,Unicode,utf-8之间的关系:

  1. ASCII是计算机刚刚起步的时候用的编码方式,特点是可以表示的字符特别少,但简单易用。
  2. Unicode是随着计算机发展,ASCII已经无法表示世界各国这么多文字而出现的一个新的编码方式,优点是编码快速,缺点是占用内存大。(如果你的文本全都是英语,如果用Unicode时,每个字符占用两个字节。而用ASCII每个字符只占用一个字节);
  3. 为了解决Unicode的内存占用大问题,出现了utf-8,utf-8可以根据字符的类型,自动选择最优编码。但缺点是编码速度慢。

所以有了令人容易搞混的问题出现:
什么时候用utf-8(这里utf-8已经包括ASCII,ASCII编码实际上可以被看成是UTF-8编码的一部分)编码方式?什么时候用Unicode编码方式?

什么时候用utf-8编码?
答案很显然:对内存消耗要求高的,对速度要求不高的场景下用utf-8。(注意:这里的速度是指cpu运算速度)。
那你就想啊,什么时候对内存消耗要求高?很容易地就想到当我们要保存在硬盘的时候肯定是想占用空间越少越好啊,当我们在网络上传输肯定也想占用空间越少越好啊,所以,当我们的数据保存在硬盘的时候,当我们数据要在网络传输的时候,用得就是utf-8编码。

什么时候用Unicode?
答案很显然:对速度要求特别高的,相对之下占用空间大小可以稍微妥协的场景下用Unicode编码。
我们知道,数据想被处理,首先得加载都内存上,这样,cpu才能以非常惊人的速度在内存上获取要处理的数据。这样,我们就轻易知道,当数据被加载到内存上时,在内存中的编码方式是Unicode。

我们来个总结:

  1. 我们平时电脑磁盘中的一个文件(abc.txt)其实是以utf-8编码方式存储的,当我们打开这个文件时,这个文件在加载到内存的时候会转变为Unicode编码方式。
    在这里插入图片描述
  2. 浏览网页的时候,服务器会把动态生成的Unicode内容转换为UTF-8再传输到浏览器(所以你看到很多网页的源码上会有类似(meta charset=“UTF-8” )的信息,表示该网页正是用的UTF-8编码。):

在这里插入图片描述

三. Python字符串

在最新的Python 3版本中,字符串是以Unicode编码的,也就是说,Python的字符串支持多语言。

这里,我重现这句话:在python中,字符串是以Unicode编码的。

这句话的一个重要的地方是:字符串,例如:

print("这句话是使用Unicode编码的,支持多语言,比如English.")
'''
这句话是使用Unicode编码的,支持多语言,比如English.
'''

解释:print函数输出的这句话在python里是使用Unicode编码的(当然它此时也在内存中,因为它现在正被加载着嘛)

那我想看看以utf-8编码方式输出的时候是什么样的,怎么做?很简单,encode函数接受一个参数,这个参数可以指定以什么方式编码!看:

"这句话是使用Unicode编码的,支持多语言,比如English.".encode('utf-8')
'''
b'\xe8\xbf\x99\xe5\x8f\xa5\xe8\xaf\x9d\xe6\x98\xaf\xe4\xbd\xbf\xe7\x94\xa8Unicode\xe7\xbc\x96\xe7\xa0\x81\xe7\x9a\x84\xef\xbc\x8c\xe6\x94\xaf\xe6\x8c\x81\xe5\xa4\x9a\xe8\xaf\xad\xe8\xa8\x80\xef\xbc\x8c\xe6\xaf\x94\xe5\xa6\x82English.'
'''

看这个输出十分有趣,首先,输出是以b开头的,说明这是一段bytes。(bytes的作用请看下去)有没有想起在前面说过的utf-8是向下兼容ASCII码的?你看输出中的英文Unicode和English就被原样输出,而ASCII码不能识别的中文,则用utf-8编码方式来表示,如\xe8,\xbf等等。

那Unicode编码方式用得好好的,可以直接混合输出英文和中文等多种语言,换成utf-8输出字符只有英文能让我们看懂,中文变成了难以分辨的十六进制(\xe8\xbf\x99\xe5\x8f\xa5\xe8…),我们为什么还要有utf-8编码方式呢?
想到这个问题说明你已经get到点了。

你想,utf-8编码方式的优点是什么?就是省内存啊!

那么,由于Python的字符串类型是str,在内存中以Unicode编码的,一个字符对应若干个字节。如果要在网络上传输,或者保存到磁盘上,就需要把Unicode编码的str变为以字节为单位的bytes,而通过utf-8编码或者ASCII码编码生成的结果就是以字节为单位的bytes。

这句话这么长无非就重复一个观点:

python中的str是以Unicode编码的(注意,既然我们能看到str,说明这个python文件已经被打开了,即已经加载到内存上),如果要在网络上传输,或者保存到磁盘上,就得转换为utf-8编码方式。

这里,我通过讲解三个例子,你们体会体会:

'I love computer'.encode('ascii')
'''
b'I love computer'
'''

解释:由于’I love computer’是纯英语,所以可以用ASCII编码。 再看:‘I love computer’和’b’I
love computer’'有什么不同?没错,多了一个b。这个b大有玄妙之处:

  1. 'I love computer’是python中的str,是以Unicode方式编码的。
  2. ‘b’I love computer’'也是python中的str,但它是以ASCII码编码的。

那能不能用utf-8编码’I love computer’呢?答案显然(ASCII编码实际上可以被看成是UTF-8编码的一部分):

'I love computer'.encode('utf-8')
'''
b'I love computer'
'''

好了,前面是对纯英文的str进行编码,那对中文的str编码呢?可以对中文的str进行utf-8编码,不能进行ASCII码编码:

'我喜欢计算机'.encode('utf-8')
'''
b'\xe6\x88\x91\xe5\x96\x9c\xe6\xac\xa2\xe8\xae\xa1\xe7\xae\x97\xe6\x9c\xba'
'''

'我喜欢计算机'.encode('ascii')
'''
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-5: ordinal not in range(128)
'''

四. 文件中的中文解决方案

  1. 统一编码,统一文件系统和解释器的编码
    【注意】:但是我们互联网交换使用的编码是utf-8,所以gbk当然不推荐。

  2. 中文用 unicode 表示(推荐)
    都知道每一个字符在计算机中都有唯一的一个编号,也就是Unicode号,用它就可以不管编码的事儿了。

  3. 把中文强制转换为GBK或者unicode编码
    强制转换为unicode编码,在 Python 中编码是可以互相转换的,比如从utf-8转换为gbk。不同编码之间不能直接转换,需要通过unicode字符集中间过渡下,从上面基础知识可知unicode是一种字符集,不属于编码,而utf-8是具体实现unicode思想的一种编码。utf-8转换为unicode是一种解码过程,通过decode可从utf-8解码成unicode。
    强制转换为gbk编码,上一步已经从utf-8转换为unicode了,从unicode是编码的过程,通过encode实现。

总结:

windows cmd 窗口下不支持utf-8,想要显示中文必须转换为gbk或者unicode,而 Python idle 中这三种编码都支持。中文乱码的出现都是由于编码不一致导致的,存储的是用utf-8,打印的时候用gbk就会乱码了,所有要保证不乱码尽量保持统一,建议全部使用unicode。

  1. 文件存储为utf-8格式,编码声明为utf-8,# coding:utf-8
  2. 出现汉字的地方前面加 u,转换成Unicode号
  3. 不同编码之间不能直接转换,要经过unicode中间跳转
  4. cmd 下不支持utf-8编码
  5. raw_input提示字符串只能为gbk编码
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值