python中编码问题

写在前面:

本文是很基础的东西,这些基础的东西有一个特点,看一遍会了,但其中很多精髓其实被忽略了,建议你货比三家,细细品尝编码之美。还有,这文章是我熬夜写的,可能有错,请批判性阅读,谢谢。

0x00:为社么会出现多种编码?

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

因此,需要用一种方法,将二进制转为我们能看懂的东西。编码就应运而生了。


0x01:编码发展历史

第一阶段:

在计算机中,所有的数据只可能是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码中没用到的第一位给用了,即:

  1. ASCII码用一个字节的后七位,表示范围是0-127;
  2. 欧洲人把这个字节的第一位也用了,表示范围0-255。除去原本的0-127,剩下128-255.128-159之间为控制字符,160-255位文字符号,其中包括了西欧语言、希腊语、泰语、阿拉伯语、希伯来语。砖家们决定把他们的编码名称叫做Latin1,后面由于欧洲统一制定ISO标准,所以又有了一个ISO的名称,即ISO-8859-1。

第三阶段

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

问题又来了:

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

因此,Unicode应运而生。Unicode把所有语言都统一到一套编码里,这样就不会再有乱码问题了。

到了这里:已经知道的编码方式主要有两种:ASCIIUnicode

现在,捋(lǚ)一捋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编码就能节省空间。
字符 ASCII Unicode UTF-8
A 01000001 00000000 01000001 01000001
x 01001110 00101101 11100100 10111000 10101101

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


0x10: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码的?你看输出中的英文UnicodeEnglish就被原样输出,而ASCII码不能识别的中文,则用utf-8编码方式来表示,如\xe8,\xbf等等。


那Unicode编码方式用得好好的,可以直接混合输出英文和中文等多种语言,换成ctf-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')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-5: ordinal not in range(128)

这几个例子很妙,你细细看几遍吧。 (认准有b和没b的区别)
全文完
参考:https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001431664106267f12e9bef7ee14cf6a8776a479bdec9b9000

posted @ 2018-07-20 11:01  busui  阅读(9475)  评论(2编辑  收藏  举报