一篇文章详解python的字符编码问题
一:什么是编码
将明文转换为计算机可以识别的编码文本称为“编码”。反之从计算机可识别的编码文本转回为明文为“解码”。
那么什么是明文呢,首先我们从一段信息说起,消息以人们可以理解,易懂的表示存在,我们把这个表示为明文(plain text)。对于说英文的人,纸张上打印的或者屏幕上显示的英文都算是明文。
二:都有什么编码格式?
1:ASCII(占一个字符,只支持英文)
计算机上的数据都是以二进制的形式存储的,1个字节(8比特)可以表示256种状态,英文只有26个字符,再加上一些特殊的字符,使用128个就够了,计算机就可以使用127个不同字节来表示英文文字,这就是ASCII码
2:GB2312(占两个字符,支持6700+汉字)
计算机进入中国后,无法显示中文,一个字节已经被占满了,我国重新制定了一个编码表,将扩展的第八位对应的拉丁文全部删掉,规定一个小于127的字符与原来的意义相同,当两个大于127的字符连接在一起的时候,就表示一个汉字,前面一个字节为高字节,后面一个字节为低字节,这样就可以表示7000多汉字,这种编码叫做GB2312。GB2312是对ASCII的中文扩展
3:GBK和GB18030(GB2312的升级版,支持21000+汉字)
由于汉字的数量太大,GB2312是不能满足需求,后面规定只要第一个字节大于127就固定表示一个汉字,不管后面的是不是扩展字符里面的内容,扩展后的编码称为GBK,GBK包括了GB2312的所有内容,同时增加了近20000个新的汉字和符号
4:Shift-JIS 日本编码(这里不过多解释)
5:TIS-620泰国编码(这里不过多解释)
6:ks_c_5601-1987韩国编码(这里不过多解释)
由于每个国家都有自己的字符,所以其对应关系也涵盖了自己国家的字符,但是以上编码都存在局限性,即:仅涵盖本国字符,无其他国家字符的对应关系。应运而生出现了万国码,他涵盖了全球所有的文字和二进制的对应关系
7:Unicode(2-4字节,已经收录了136690个字符,并一直扩展)
在uincode出现之前,每隔国家都搞自己的编码,彼此之间互不支持,带来了许多不方便,国际标准组织提出来一个统一的编码标准:unicode
unicode用两个字符来表示一个字符,可以提供65535种字符,足够覆盖世界上所有的字符
8:UTF-8(Unicode Transformation Format)
unicode的出现,提供了统一的标准,但对于英文世界来说,一个字节完全够用,如果使用unicode会浪费大量的空间,为了解决这个问题提出来utf-8,一种针对unicode的可变长度字符串,可以使用1-4个字符表示一个符号,根据不同的符号变化字节长度,当字符在ASCII编码范围内,用一个字节表示,兼用ASCII。
使用这样编码的好处是,虽然内存汇总的数据都是unicode,但是当数据保存到磁盘或者用于网络传输时,使用utf-8会节省更多的流量和硬盘空间。
- UTF-8: 使用1、2、3、4个字节表示所有字符;优先使用1个字符、无法满足则使增加一个字节,最多4个字节。英文占1个字节、欧洲语系占2个、东亚占3个,其它及特殊字符占4个
- UTF-16: 使用2、4个字节表示所有字符;优先使用2个字节,否则使用4个字节表示。
- UTF-32: 使用4个字节表示所有字符;
unicode和utf-8的关系:unicode是内存编码表示方案,而utf-8是如何保存和传输unicode的方案
三:编码的转换
虽然国际语言是英语 ,但大家在自己的国家依然说自已的语言,不过出了国, 你就得会英语
编码也一样,虽然有了unicode and utf-8 , 但是由于历史问题,各个国家依然在大量使用自己的编码,比如中国的windows,默认编码依然是gbk,而不是utf-8
基于此,如果中国的软件出口到美国,在美国人的电脑上就会显示乱码,因为他们没有gbk编码。
若想让中国的软件可以正常的在 美国人的电脑上显示,只有以下2条路可走:
- 让美国人的电脑上都装上gbk编码
- 把你的软件编码以utf-8编码
第1种方法几乎不可能实现,第2种方法比较简单。 但是也只能是针对新开发的软件。 如果你之前开发的软件就是以gbk编码的,上百万行代码可能已经写出去了,重新编码成utf-8格式也会费很大力气。
so , 针对已经用gbk开发完毕的项目,以上2种方案都不能轻松的让项目在美国人电脑上正常显示,难道没有别的办法了么?
有, 还记得我们、、讲unicode其中一个功能是其包含了跟全球所有国家编码的映射关系,意思就是,你写的是gbk的“学习”,但是unicode能自动知道它在unicode中的“学习”的编码是什么,如果这样的话,那是不是意味着,无论你以什么编码存储的数据 ,只要你的软件在把数据从硬盘读到内存里,转成unicode来显示,就可以了。
由于所有的系统、编程语言都默认支持unicode,那你的gbk软件放到美国电脑 上,加载到内存里,变成了unicode,中文就可以正常展示啦。
unicode与gbk的映射表 http://www.unicode.org/charts/
四:python2.x的编码
在python2.x中,有两种字符串类型:str类型和unicode类型。这两个类型只是python定义的两个名字,关键还要看这两种数据类型在内存中的存储方法是什么
str和unicode都是basestring的子类。严格意义上说,str其实是字节串,它是unicode经过编码后的字节组成的序列。
而unicode是一个字符串,str是unicode这个字符串经过编码(utf8,gbk等)后的字节组成的序列。
unicode才是真正意义上的字符串,对字节串str使用正确的字符编码进行解码后获得
在Py2里,str=bytes。python2的字符串其实更应该称为字节串
py2编码的最大特点是Python 2 将会自动的将bytes数据解码成 unicode 字符串
所以在2里我们可以将字节与字符串拼接。
Python 2.7.14 (v2.7.14:84471935ed, Sep 16 2017, 20:25:58) [MSC v.1500 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> s = '学习' >>> print type(s) <type 'str'> >>> print repr(s) '\xd1\xa7\xcf\xb0'
>>> s = u'学习'
>>> print type(s)
<type 'unicode'>
>>> print repr(s)
u'\u5b66\u4e60'
由上面的例子可以看出str和unicode分别存储的是字节数据和unicode数据
但是,python2.x悄悄掩盖掉了byte到unicode的转换,只要数据全部是ASCII的话,所有的转换都是正确的,一个一个非ASCII的字符进入你的程序,那么默认的解码将会失效,从而造成unicodedecodeerror的错误,py2编码让程序在出路ASCII的时候非常简单,付出的代价就是在处理非ASCII的时候将会失败。
由于Python创始人在开发初期认知的局限性,其并未预料到python能发展成一个全球流行的语言,导致其开发初期并没有把支持全球各国语言当做重要的事情来做,所以就轻佻的把ASCII当做了默认编码。 当后来大家对支持汉字、日文、法语等语言的呼声越来越高时,Python于是准备引入unicode,但若直接把默认编码改成unicode的话是不现实的, 因为很多软件就是基于之前的默认编码ASCII开发的,编码一换,那些软件的编码就都乱了。所以Python 2 就直接 搞了一个新的字符类型,就叫unicode类型,比如你想让你的中文在全球所有电脑上正常显示,在内存里就得把字符串存成unicode类型
五:python3.x的编码
python3.x也有两种数据类型,str和bytes;str类型存unicode数据,bytes类型存bytes数据,
python3.x将utf-8或者gbk等编码的字节数据转化为python3.x的str类型,utf-8编码的bytes<-->str
python2.x将utf-8或者gbk等编码的字节数据转化为python2.x的unicode类型,utf-8编码的str<-->unicode
python3.x的编码思想就是它清晰的将文本和二进制区分开了,不会对bytes字节进行自动编码。文本总是unicode,由str类型表示,二进制数据则由bytes类型表示,Python 3不会以任意隐式的方式混用str和bytes,将两者明确地区分开。基于此,Python3中不能拼接字符串和字节包,也不可以在字节包里搜索字符串(反之亦然),也不能向使用字符串参数的函数中传入字节包参数(反之亦然)。
python3 执行代码的过程
- 解释器找到代码文件,把代码字符串按文件头定义的编码加载到内存,转成unicode
- 把代码字符串按照语法规则进行解释,
- 所有的变量字符都会以unicode编码声明
######在python2.x####### print(b'hello'+'world') 会输出 helloworld ######在python3.x####### print(b'hello'+'world') 会输出 TypeError: can't concat str to bytes
本来这样就可以结束了,但是上面的utf-8编码之所以能在windows gbk的终端下显示正常,是因为到了内存里python解释器把utf-8转成了unicode , 但是这只是python3, 并不是所有的编程语言在内存里默认编码都是unicode,比如 万恶的python2 就不是, 它的默认编码是ASCII,想写中文,就必须声明文件头的coding为gbk or utf-8, 声明之后,python2解释器仅以文件头声明的编码去解释你的代码,加载到内存后,并不会主动帮你转为unicode,也就是说,你的文件编码是utf-8,加载到内存里,你的变量字符串就也是utf-8, 这意味着什么你知道么?。。。意味着,你以utf-8编码的文件,在windows是乱码。
乱是正常的,不乱才不正常,因为只有2种情况 ,你的windows上显示才不会乱
- 字符串以GBK格式显示
- 字符串是unicode编码
既然Python2并不会自动的把文件编码转为unicode存在内存里, 那就只能使出最后一招了,你自己人肉转。Py3 自动把文件编码转为unicode必定是调用了什么方法,这个方法就是,decode(解码) 和encode(编码)
时间来到2008年,python发展已近20年,创始人龟叔越来越觉得python里的好多东西已发展的不像他的初衷那样,开始变得臃肿、不简洁、且有些设计让人摸不到头脑,比如unicode 与str类型,str 与bytes类型的关系,这给很多python程序员造成了困扰。
龟叔再也忍不了,像之前一样的修修补补已不能让Python变的更好,于是来了个大变革,Python3横空出世,不兼容python2,python3比python2做了非常多的改进,其中一个就是终于把字符串变成了unicode,文件默认编码变成了utf-8,这意味着,只要用python3,无论你的程序是以哪种编码开发的,都可以在全球各国电脑上正常显示,真是太棒啦!
PY3 除了把字符串的编码改成了unicode, 还把str 和bytes 做了明确区分, str 就是unicode格式的字符, bytes就是单纯二进制啦。
六:文件存储读取过程中的编码问题
对于文本编辑器word等软件,当我们在这些软件上编辑文字的时候,无论是什么语言的文字或符号,计算机都是无法识别的。
那么在保存之前数据是通过什么形式存在内存的呢?
是unicode数据,为什么要存unicode数据,这是因为无论世界上的任何字符它都有唯一编码对应,兼容性是最好的。
当我们保存了存到磁盘上的数据又是什么呢?
是通过某种编码方式编码的bytes字节串。比如utf8---一种可变长编码,很好的节省了空间;还可以是gbk等编码方式。
在我们的文本编辑器软件都有默认的保存文件的编码方式,比如utf-8,gbk等。当我们保存的时候,这些编辑软件已经"默默地"做了编码工作。
那当我们再打开这个文件时,软件又默默地给我们做了解码的工作,将数据再解码成unicode,然后就可以呈现明文给用户了!
所以,unicode是离用户更近的数据,bytes是离计算机更近的数据。
七:编码与程序运行的关系
编写python代码一般会用到pycharm ,sublime等软件,而代码文件的创建,保存,执行等过程就伴随着编码解码流程,使用pycharm创建的hello.py文件,当我们保存的时候,文件就以pycharm默认的编码方式保存到磁盘,关闭文件再打开,pycharm就会以默认的编码方式对该文件打开后读到的内容就行解码,转成unicode到内存我们就看到了我们的明文;
而如果点开运行按钮或者在命令行运行该文件时候,python解释器就会被调用,打开文件,然后将存储在磁盘上的bytes数据解码成unicode数据,这个过程和编译器是一样的,不同的是解释器将会把这些unicode数据翻译成c代码再转成二进制的数据流,最后通过控制操作系统调用cpu来执行这些二进制数据,整个过程才算结束,
python2.x默认的是ASCII码,python3.x默认的是utf-8,可以通过下面的方式查询:
import sys
print(sys.getdefaultencoding())
八,字符编码转换总结
python2.x
内存中字符默认编码是ASCII,默认文件编码也是ASCII
当声明了文件头的编码后,字符串的编码就按照文件编码来,总之,文件编码是什么,那么python2.x的str就是什么
python2.x的unicode是一个单独的类型,按u"编码"来表示
python2.x str==bytes,bytes直接是按照字符编码存成2进制格式在内存里
python3.x
字符串都是unicode
文件编码都默认是utf-8,读到内存会被python解释器自动转成unicode
bytes和str做了明确的区分
所有的unicode字符编码后都会编程bytes格式
九:print语句和print函数的区别
print语句
在python2.x中,print语句最简单的使用形式是
print hello world!
这相当于执行了
sys.stdout.write(‘’。join(map(str,[hello world!]))+'\n')
如果以逗号为分隔符,传递额外的参数,这些参数会被传递到str()函数,最终打印的时候,每个参数之间会空一行。
从2.0版本开始,python引入了print>>的语法。作用是重定向print语句最终输出的字符串的文件
例如:
print>>output 相当于 output.write(str(hello)+'\n')
print函数
如果用python来实现print的函数,他的函数定义应该是这样的
import sys def print(*objects, sep=None, end=None, file=None, flush=False): """A Python translation of the C code for builtins.print(). """ if sep is None: sep = ' ' if end is None: end = '\n' if file is None: file = sys.stdout file.write(sep.join(map(str, objects)) + end) if flush: file.flush() 函数定义
从上面的代码我们可以发现,python3.x的print 函数实现了print语句的所有特性。