这哥们的这段话说的太对了,搞Python不把编码完全搞明白,总有一天它会猝不及防坑你一把。
不过感受这哥们的答案并没把编码问题写明白,因此只好亲自动笔了。
折腾编码问题,有不少次,我觉得自已明白了,最终发现,那只不过是自圆其说而已,这一次,终于100%肯定,动笔即再也不改!
看这篇文章前,你应该已经知道了为何有编码,以及编码的种类状况python
因为每一个国家都有本身的字符,因此其对应关系也涵盖了本身国家的字符,可是以上编码都存在局限性,即:仅涵盖本国字符,无其余国家字符的对应关系。应运而生出现了万国码,他涵盖了全球全部的文字和二进制的对应关系。程序员
Unicode 起到了2个做用:编程
Unicode解决了字符和二进制的对应关系,可是使用unicode表示一个字符,太浪费空间。例如:利用unicode表示“Python”须要12个字节才能表示,比原来ASCII表示增长了1倍。
因为计算机的内存比较大,而且字符串在内容中表示时也不会特别大,因此内容可使用unicode来处理,可是存储和网络传输时通常数据都会很是多,那么增长1倍将是没法容忍的!!!
为了解决存储和网络传输的问题,出现了Unicode Transformation Format,学术名UTF,即:对unicode中的进行转换,以便于在存储和网络传输时能够节省空间!windows
总结:UTF 是为unicode编码 设计 的一种 在存储 和传输时节省空间的编码方案。网络
不管以什么编码在内存里显示字符,存到硬盘上都是2进制。编程语言
ascii编码(美国): l 0b1101100 o 0b1101111 v 0b1110110 e 0b1100101 GBK编码(中国): 老 0b11000000 0b11001111 男 0b11000100 0b11010000 孩 0b10111010 0b10100010 Shift_JIS编码(日本): 私 0b10001110 0b10000100 は 0b10000010 0b11001101 ks_c_5601-1987编码(韩国): 나 0b10110011 0b10101010 는 0b10110100 0b11000010 TIS-620编码(泰国): ฉัน 0b10101001 0b11010001 0b10111001 ...
要注意的是,存到硬盘上时是以何种编码存的,再从硬盘上读出来时,就必须以何种编码读,要否则就乱了。。
虽然国际语言是英语 ,但你们在本身的国家依然说自已的语言,不过出了国, 你就得会英语 编码也同样,虽然有了unicode and utf-8 ,可是因为历史问题,各个国家依然在大量使用本身的编码,好比中国的windows,默认编码依然是gbk,而不是utf-8。
基于此,若是中国的软件出口到美国,在美国人的电脑上就会显示乱码,由于他们没有gbk编码。
若想让中国的软件能够正常的在 美国人的电脑上显示,只有如下2条路可走:学习
第1种方法几乎不可能实现,第2种方法比较简单。 可是也只能是针对新开发的软件。 若是你以前开发的软件就是以gbk编码的,上百万行代码可能已经写出去了,从新编码成utf-8格式也会费很大力气。编码
so , 针对已经用gbk开发完毕的项目,以上2种方案都不能轻松的让项目在美国人电脑上正常显示,难道没有别的办法了么?操作系统
有, 还记得咱们讲unicode其中一个功能是其包含了跟全球全部国家编码的映射关系,意思就是,你写的是gbk的“路飞学城”,可是unicode能自动知道它在unicode中的“路飞学城”的编码是什么,若是这样的话,那是否是意味着,不管你以什么编码存储的数据,只要你的软件在把数据从硬盘读到内存里,转成unicode来显示,就能够了。因为全部的系统、编程语言都默认支持unicode,那你的gbk软件放到美国电脑上,加载到内存里,变成了unicode,中文就能够正常展现啦。设计
这个表你本身也能够下载下来
unicode与gbk的映射表 http://www.unicode.org/charts/
在看实际代码的例子前,咱们来聊聊,python3 执行代码的过程
实际代码演示,在py3上 把你的代码以utf-8编写, 保存,而后在windows上执行。
s = '路飞学城' print(s)
so ,一切都很美好,到这里,咱们关于编码的学习按说就能够结束了。
可是,如生活同样,美好的表面下,老是隐藏着不尽如人意,上面的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上显示才不会乱。
既然Python2并不会自动的把文件编码转为unicode存在内存里, 那就只能使出最后一招了,你本身人肉转。Py3 自动把文件编码转为unicode一定是调用了什么方法,这个方法就是,decode(解码) 和encode(编码)
UTF-8 --> decode 解码 --> Unicode Unicode --> encode 编码 --> GBK / UTF-8 ..
decode示例
encode 示例
记住下图规则
unicode字符是有专门的unicode类型来判断的,可是utf-8,gbk编码的字符都是str,你若是分辨出来的当前的字符串数据是何种编码的呢? 有人说能够经过字节长度判断,由于utf-8一个中文占3字节,gbk一个占2字节。
靠上面字节个数,虽然也能大致判断是什么类型,但总以为不是很专业。
怎么才能精确的验证一个字符的编码呢,就是拿这些16进制的数跟编码表里去匹配。
“路飞学城”的unicode编码的映射位置是 u'\u8def\u98de\u5b66\u57ce' ,‘\u8def’ 就是‘路’,到表里搜一下。
“路飞学城”对应的GBK编码是'\xc2\xb7\xb7\xc9\xd1\xa7\xb3\xc7' ,2个字节一个中文,"路" 的二进制 "\xc2\xb7"是4个16进制,正好2字节,拿它到unicode映射表里对一下, 发现是G0-4237,并非\xc2\xb7呀。。。擦。演砸了吧。。
再查下“飞” \u98de ,对应的是G0-3749, 跟\xb7\xc9也对不上。
虽然对不上, 但好\xc2\xb7 和G0-4237中的第2位的2和第4位的7对上了,“飞”字也是同样,莫非巧合?
把他们都转成2进制显示试试
路 C 2 8 4 2 1 8 4 2 1 <strong>1 1 0 0 0 0 1 0</strong> B 7 8 4 2 1 8 4 2 1 <strong>1 0 1 1 0 1 1 1</strong> 飞 B 7 8 4 2 1 8 4 2 1 1 0 1 1 0 1 1 1 C 9 8 4 2 1 8 4 2 1 1 1 0 0 1 0 0 1
这个“路”仍是跟G0-4237对不上呀,没错, 但若是你把路\xc2\xb7的每一个二进制字节的左边第一个bit变成0试试呢, 我擦,加起来就真的是4237了呀。。难道又是巧合???
必然不是,是由于,GBK的编码表示形式决定的。。由于GBK编码在设计初期就考虑到了要兼容ASCII,即若是是英文,就用一个字节表示,2个字节就是中文,但如何区别连在一块儿的2个字节是表明2个英文字母,仍是一个中文汉字呢? 中国人如此聪明,决定,2个字节连在一块儿,若是每一个字节的第1位(也就是至关于128的那个2进制位)若是是1,就表明这是个中文,这个首位是128的字节被称为高字节。 也就是2个高字节连在一块儿,必然就是一个中文。 你怎么如此笃定?由于0-127已经表示了英文的绝大部分字符,128-255是ASCII的扩展表,表示的都是极特殊的字符,通常没什么用。因此中国人就直接拿来用了。
问:那为何上面 "\xc2\xb7"的2进制要把128所在的位去掉才能与unicode编码表里的G0-4237匹配上呢?
这只能说是unicode在映射表的表达上直接忽略了高字节,但真正映射的时候 ,确定仍是须要用高字节的哈。
在python 2 上写字符串
>>> s = "路飞" >>> print s 路飞 >>> s '\xe8\xb7\xaf\xe9\xa3\x9e'
虽然说打印的是路飞,但直接调用变量s,看到的倒是一个个的16进制表示的二进制字节,咱们怎么称呼这样的数据呢?直接叫二进制么?也能够, 但相比于010101,这个数据串在表示形式上又把2进制转成了16进制来表示,这是为何呢? 哈,为的就是让人们看起来更可读。咱们称之为bytes类型,即字节类型, 它把8个二进制一组称为一个byte,用16进制来表示。
说这个有什么意思呢?
想告诉你一个事实, 就是,python2的字符串其实更应该称为字节串。 经过存储方式就能看出来, 但python2里还有一个类型是bytes呀,难道又叫bytes又叫字符串? 嗯 ,是的,在python2里,bytes == str , 其实就是一回事。
除此以外呢, python2里还有个单独的类型是unicode , 把字符串解码后,就会变成unicode
>>> s '\xe8\xb7\xaf\xe9\xa3\x9e' #utf-8 >>> s.decode('utf-8') u'\u8def\u98de' #unicode 在unicode编码表里对应的位置 >>> print(s.decode('utf-8')) 路飞 #unicode 格式的字符
因为Python创始人在开发初期认知的局限性,其并未预料到python能发展成一个全球流行的语言,致使其开发初期并无把支持全球各国语言当作重要的事情来作,因此就轻佻的把ASCII当作了默认编码。 当后来你们对支持汉字、日文、法语等语言的呼声愈来愈高时,Python因而准备引入unicode,但若直接把默认编码改为unicode的话是不现实的, 由于不少软件就是基于以前的默认编码ASCII开发的,编码一换,那些软件的编码就都乱了。因此Python 2 就直接 搞了一个新的字符类型,就叫unicode类型,好比你想让你的中文在全球全部电脑上正常显示,在内存里就得把字符串存成unicode类型。
>>> s = "路飞" >>> s '\xe8\xb7\xaf\xe9\xa3\x9e' >>> s2 = s.decode("utf-8") >>> s2 u'\u8def\u98de' >>> type(s2) <type 'unicode'>
时间来到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就是单纯二进制啦。
最后一个问题,为何在py3里,把unicode编码后,字符串就变成了bytes格式? 你直接给我直接打印成gbk的字符展现很差么?我想其实py3的设计真是煞费苦心,就是想经过这样的方式明确的告诉你,想在py3里看字符,必须得是unicode编码,其它编码一概按bytes格式展现。
好吧,就说这么多吧。
最后再提示一下,Python只要出现各类编码问题,无非是哪里的编码设置出错了
常见编码错误的缘由有: