py编码终极版
查看原文:猛擊這里
說(shuō)起python編碼,真是句句心酸。算起來(lái),反復(fù)折騰兩個(gè)來(lái)月了。萬(wàn)幸的是,終于梳理清楚了。作為一個(gè)共產(chǎn)主義者,一定要分享給大家。如果你還在因?yàn)榫幋a而頭痛,那么趕緊跟著我咱們一起來(lái)揭開(kāi)py編碼的真相吧!
一 什么是編碼?
基本概念很簡(jiǎn)單。首先,我們從一段信息即消息說(shuō)起,消息以人類(lèi)可以理解、易懂的表示存在。我打算將這種表示稱(chēng)為“明文”(plain text)。對(duì)于說(shuō)英語(yǔ)的人,紙張上打印的或屏幕上顯示的英文單詞都算作明文。
其次,我們需要能將明文表示的消息轉(zhuǎn)成另外某種表示,我們還需要能將編碼文本轉(zhuǎn)回成明文。從明文到編碼文本的轉(zhuǎn)換稱(chēng)為“編碼”,從編碼文本又轉(zhuǎn)回成明文則為“解碼”。
編碼問(wèn)題是個(gè)大問(wèn)題,如果不徹底解決,它就會(huì)像隱藏在叢林中的小蛇,時(shí)不時(shí)地咬你一口。那么到底什么是編碼呢?//ASCII記住一句話(huà):計(jì)算機(jī)中的所有數(shù)據(jù),不論是文字、圖片、視頻、還是音頻文件,本質(zhì)上最終都是按照類(lèi)似 01010101 的二進(jìn)制存儲(chǔ)的。再說(shuō)簡(jiǎn)單點(diǎn),計(jì)算機(jī)只懂二進(jìn)制數(shù)字!所以,目的明確了:如何將我們能識(shí)別的符號(hào)唯一的與一組二進(jìn)制數(shù)字對(duì)應(yīng)上?于是美利堅(jiān)的同志想到通過(guò)一個(gè)電平的高低狀態(tài)來(lái)代指0或1,八個(gè)電平做為一組就可以表示出256種不同狀態(tài),每種狀態(tài)就唯一對(duì)應(yīng)一個(gè)字符,比如A--->00010001,而英文只有26個(gè)字符,算上一些特殊字符和數(shù)字,128個(gè)狀態(tài)也夠用了;每個(gè)電平稱(chēng)為一個(gè)比特為,約定8個(gè)比特位構(gòu)成一個(gè)字節(jié),這樣計(jì)算機(jī)就可以用127個(gè)不同字節(jié)來(lái)存儲(chǔ)英語(yǔ)的文字了。這就是ASCII編碼。擴(kuò)展ANSI編碼剛才說(shuō)了,最開(kāi)始,一個(gè)字節(jié)有八位,但是最高位沒(méi)用上,默認(rèn)為0;后來(lái)為了計(jì)算機(jī)也可以表示拉丁文,就將最后一位也用上了,從128到255的字符集對(duì)應(yīng)拉丁文啦。至此,一個(gè)字節(jié)就用滿(mǎn)了!//GB2312計(jì)算機(jī)漂洋過(guò)海來(lái)到中國(guó)后,問(wèn)題來(lái)了,計(jì)算機(jī)不認(rèn)識(shí)中文,當(dāng)然也沒(méi)法顯示中文;而且一個(gè)字節(jié)所有狀態(tài)都被占滿(mǎn)了,萬(wàn)惡的帝國(guó)主義亡我之心不死啊!我黨也是棒,自力更生,自己重寫(xiě)一張表,直接生猛地將擴(kuò)展的第八位對(duì)應(yīng)拉丁文全部刪掉,規(guī)定一個(gè)小于127的字符的意義與原來(lái)相同,但兩個(gè)大于127的字符連在一起時(shí),就表示一個(gè)漢字,前面的一個(gè)字節(jié)(他稱(chēng)之為高字節(jié))從0xA1用到0xF7,后面一個(gè)字節(jié)(低字節(jié))從0xA1到0xFE,這樣我們就可以組合出大約7000多個(gè)簡(jiǎn)體漢字了;這種漢字方案叫做 “GB2312”。GB2312 是對(duì) ASCII 的中文擴(kuò)展。//GBK 和 GB18030編碼但是漢字太多了,GB2312也不夠用,于是規(guī)定:只要第一個(gè)字節(jié)是大于127就固定表示這是一個(gè)漢字的開(kāi)始,不管后面跟的是不是擴(kuò)展字符集里的內(nèi)容。結(jié)果擴(kuò)展之后的編碼方案被稱(chēng)為 GBK 標(biāo)準(zhǔn),GBK 包括了 GB2312 的所有內(nèi)容,同時(shí)又增加了近20000個(gè)新的漢字(包括繁體字)和符號(hào)。//UNICODE編碼:很多其它國(guó)家都搞出自己的編碼標(biāo)準(zhǔn),彼此間卻相互不支持。這就帶來(lái)了很多問(wèn)題。于是,國(guó)際標(biāo)誰(shuí)化組織為了統(tǒng)一編碼:提出了標(biāo)準(zhǔn)編碼準(zhǔn)則:UNICODE 。UNICODE是用兩個(gè)字節(jié)來(lái)表示為一個(gè)字符,它總共可以組合出65535不同的字符,這足以覆蓋世界上所有符號(hào)(包括甲骨文)//utf8:unicode都一統(tǒng)天下了,為什么還要有一個(gè)utf8的編碼呢?大家想,對(duì)于英文世界的人們來(lái)講,一個(gè)字節(jié)完全夠了,比如要存儲(chǔ)A,本來(lái)00010001就可以了,現(xiàn)在吃上了unicode的大鍋飯,得用兩個(gè)字節(jié):00000000 00010001才行,浪費(fèi)太嚴(yán)重!基于此,美利堅(jiān)的科學(xué)家們提出了天才的想法:utf8.UTF-8(8-bit Unicode Transformation Format)是一種針對(duì)Unicode的可變長(zhǎng)度字符編碼,它可以使用1~4個(gè)字節(jié)表示一個(gè)符號(hào),根據(jù)不同的符號(hào)而變化字節(jié)長(zhǎng)度,當(dāng)字符在ASCII碼的范圍時(shí),就用一個(gè)字節(jié)表示,所以是兼容ASCII編碼的。這樣顯著的好處是,雖然在我們內(nèi)存中的數(shù)據(jù)都是unicode,但當(dāng)數(shù)據(jù)要保存到磁盤(pán)或者用于網(wǎng)絡(luò)傳輸時(shí),直接使用unicode就遠(yuǎn)不如utf8省空間啦!這也是為什么utf8是我們的推薦編碼方式。Unicode與utf8的關(guān)系:一言以蔽之:Unicode是內(nèi)存編碼表示方案(是規(guī)范),而UTF是如何保存和傳輸U(kuò)nicode的方案(是實(shí)現(xiàn))這也是UTF與Unicode的區(qū)別。補(bǔ)充:utf8是如何節(jié)約硬盤(pán)和流量的
s="I'm 苑昊"你看到的unicode字符集是這樣的編碼表:
I 0049 ' 0027 m 006d0020 苑 82d1 昊 660a每一個(gè)字符對(duì)應(yīng)一個(gè)十六進(jìn)制數(shù)字。
計(jì)算機(jī)只懂二進(jìn)制,因此,嚴(yán)格按照unicode的方式(UCS-2),應(yīng)該這樣存儲(chǔ):
這個(gè)字符串總共占用了12個(gè)字節(jié),但是對(duì)比中英文的二進(jìn)制碼,可以發(fā)現(xiàn),英文前9位都是0!浪費(fèi)啊,浪費(fèi)硬盤(pán),浪費(fèi)流量。怎么辦?UTF8:
I 01001001 ' 00100111 m 0110110100100000 苑 11101000 10001011 10010001 昊 11100110 10011000 10001010utf8用了10個(gè)字節(jié),對(duì)比unicode,少了兩個(gè),因?yàn)槲覀兊某绦蛴⑽臅?huì)遠(yuǎn)多于中文,所以空間會(huì)提高很多!
記住:一切都是為了節(jié)省你的硬盤(pán)和流量?! ?/p>
二 py2的string編碼
在py2中,有兩種字符串類(lèi)型:str類(lèi)型和unicode類(lèi)型;注意,這僅僅是兩個(gè)名字,python定義的兩個(gè)名字,關(guān)鍵是這兩種數(shù)據(jù)類(lèi)型在程序運(yùn)行時(shí)存在內(nèi)存地址的是什么?
我們來(lái)看一下:
#coding:utf8 s1='苑'print type(s1) # <type 'str'> print repr(s1) #'\xe8\x8b\x91 s2=u'苑' print type(s2) # <type 'unicode'> print repr(s2) # u'\u82d1'內(nèi)置函數(shù)repr可以幫我們?cè)谶@里顯示存儲(chǔ)內(nèi)容。原來(lái),str和unicode分別存的是字節(jié)數(shù)據(jù)和unicode數(shù)據(jù);那么兩種數(shù)據(jù)之間是什么關(guān)心呢?如何轉(zhuǎn)換呢?這里就涉及到編碼(encode)和解碼(decode)了
s1=u'苑' print repr(s1) #u'\u82d1' b=s1.encode('utf8') print b print type(b) #<type 'str'> print repr(b) #'\xe8\x8b\x91' s2='苑昊' u=s2.decode('utf8') print u # 苑昊 print type(u) # <type 'unicode'> print repr(u) # u'\u82d1\u660a'#注意 u2=s2.decode('gbk') print u2 #鑻戞槉print len('苑昊') #6無(wú)論是utf8還是gbk都只是一種編碼規(guī)則,一種把unicode數(shù)據(jù)編碼成字節(jié)數(shù)據(jù)的規(guī)則,所以u(píng)tf8編碼的字節(jié)一定要用utf8的規(guī)則解碼,否則就會(huì)出現(xiàn)亂碼或者報(bào)錯(cuò)的情況。
py2編碼的特色:
#coding:utf8print '苑昊' # 苑昊 print repr('苑昊')#'\xe8\x8b\x91\xe6\x98\x8a'print (u"hello"+"yuan")#print (u'苑昊'+'最帥') #UnicodeDecodeError: 'ascii' codec can't decode byte 0xe6# in position 0: ordinal not in range(128)Python 2 悄悄掩蓋掉了 byte 到 unicode 的轉(zhuǎn)換,只要數(shù)據(jù)全部是 ASCII 的話(huà),所有的轉(zhuǎn)換都是正確的,一旦一個(gè)非 ASCII 字符偷偷進(jìn)入你的程序,那么默認(rèn)的解碼將會(huì)失效,從而造成 UnicodeDecodeError 的錯(cuò)誤。py2編碼讓程序在處理 ASCII 的時(shí)候更加簡(jiǎn)單。你復(fù)出的代價(jià)就是在處理非 ASCII 的時(shí)候?qū)?huì)失敗。
三 py3的string編碼
python3 renamed the unicode type to str ,the old str type has been replaced by bytes.
?py3也有兩種數(shù)據(jù)類(lèi)型:str和bytes; ?str類(lèi)型存unicode數(shù)據(jù),bytse類(lèi)型存bytes數(shù)據(jù),與py2比只是換了一下名字而已。
import jsons='苑昊' print(type(s)) #<class 'str'> print(json.dumps(s)) # "\u82d1\u660a" b=s.encode('utf8') print(type(b)) # <class 'bytes'> print(b) # b'\xe8\x8b\x91\xe6\x98\x8a' u=b.decode('utf8') print(type(u)) #<class 'str'> print(u) #苑昊 print(json.dumps(u)) #"\u82d1\u660a"print(len('苑昊')) # 2?
py3的編碼哲學(xué):
Python 3最重要的新特性大概要算是對(duì)文本和二進(jìn)制數(shù)據(jù)作了更為清晰的區(qū)分,不再會(huì)對(duì)bytes字節(jié)串進(jìn)行自動(dòng)解碼。文本總是Unicode,由str類(lèi)型表示,二進(jìn)制數(shù)據(jù)則由bytes類(lèi)型表示。Python 3不會(huì)以任意隱式的方式混用str和bytes,正是這使得兩者的區(qū)分特別清晰。你不能拼接字符串和字節(jié)包,也無(wú)法在字節(jié)包里搜索字符串(反之亦然),也不能將字符串傳入?yún)?shù)為字節(jié)包的函數(shù)(反之亦然)。
#print('alvin'+u'yuan')#字節(jié)串和unicode連接 py2:alvinyuan print(b'alvin'+'yuan')#字節(jié)串和unicode連接 py3:報(bào)錯(cuò) can't concat bytes to str注意:無(wú)論py2,還是py3,與明文直接對(duì)應(yīng)的就是unicode數(shù)據(jù),打印unicode數(shù)據(jù)就會(huì)顯示相應(yīng)的明文(包括英文和中文)
四 文件從磁盤(pán)到內(nèi)存的編碼(******)
說(shuō)到這,才來(lái)到我們的重點(diǎn)!
拋開(kāi)執(zhí)行執(zhí)行程序,請(qǐng)問(wèn)大家,文本編輯器大家都是用過(guò)吧,如果不懂是什么,那么word總用過(guò)吧,ok,當(dāng)我們?cè)趙ord上編輯文字的時(shí)候,不管是中文還是英文,計(jì)算機(jī)都是不認(rèn)識(shí)的,那么在保存之前數(shù)據(jù)是通過(guò)什么形式存在內(nèi)存的呢?yes,就是unicode數(shù)據(jù),為什么要存unicode數(shù)據(jù),這是因?yàn)樗拿肿顚?#xff1a;萬(wàn)國(guó)碼!解釋起來(lái)就是無(wú)論英文,中文,日文,拉丁文,世界上的任何字符它都有唯一編碼對(duì)應(yīng),所以兼容性是最好的。
好,那當(dāng)我們保存了存到磁盤(pán)上的數(shù)據(jù)又是什么呢?
答案是通過(guò)某種編碼方式編碼的bytes字節(jié)串。比如utf8---一種可變長(zhǎng)編碼,很好的節(jié)省了空間;當(dāng)然還有歷史產(chǎn)物的gbk編碼等等。于是,在我們的文本編輯器軟件都有默認(rèn)的保存文件的編碼方式,比如utf8,比如gbk。當(dāng)我們點(diǎn)擊保存的時(shí)候,這些編輯軟件已經(jīng)"默默地"幫我們做了編碼工作。
那當(dāng)我們?cè)俅蜷_(kāi)這個(gè)文件時(shí),軟件又默默地給我們做了解碼的工作,將數(shù)據(jù)再解碼成unicode,然后就可以呈現(xiàn)明文給用戶(hù)了!所以,unicode是離用戶(hù)更近的數(shù)據(jù),bytes是離計(jì)算機(jī)更近的數(shù)據(jù)。
說(shuō)了這么多,和我們程序執(zhí)行有什么關(guān)系呢?
先明確一個(gè)概念:py解釋器本身就是一個(gè)軟件,一個(gè)類(lèi)似于文本編輯器一樣的軟件!
現(xiàn)在讓我們一起還原一個(gè)py文件從創(chuàng)建到執(zhí)行的編碼過(guò)程:
打開(kāi)pycharm,創(chuàng)建hello.py文件,寫(xiě)入
ret=1+1 s='苑昊' print(s)當(dāng)我們保存的的時(shí)候,hello.py文件就以pycharm默認(rèn)的編碼方式保存到了磁盤(pán);關(guān)閉文件后再打開(kāi),pycharm就再以默認(rèn)的編碼方式對(duì)該文件打開(kāi)后讀到的內(nèi)容進(jìn)行解碼,轉(zhuǎn)成unicode到內(nèi)存我們就看到了我們的明文;
? ? ? 而如果我們點(diǎn)擊運(yùn)行按鈕或者在命令行運(yùn)行該文件時(shí),py解釋器這個(gè)軟件就會(huì)被調(diào)用,打開(kāi)文件,然后解碼存在磁盤(pán)上的bytes數(shù)據(jù)成unicode數(shù)據(jù),這個(gè)過(guò)程和編輯器是一樣的,不同的是解釋器會(huì)再將這些unicode數(shù)據(jù)翻譯成C代碼再轉(zhuǎn)成二進(jìn)制的數(shù)據(jù)流,最后通過(guò)控制操作系統(tǒng)調(diào)用cpu來(lái)執(zhí)行這些二進(jìn)制數(shù)據(jù),整個(gè)過(guò)程才算結(jié)束。
那么問(wèn)題來(lái)了,我們的文本編輯器有自己默認(rèn)的編碼解碼方式,我們的解釋器有嗎?
當(dāng)然有啦,py2默認(rèn)ASCII碼,py3默認(rèn)的utf8,可以通過(guò)如下方式查詢(xún)
import sys print(sys.getdefaultencoding())大家還記得這個(gè)聲明嗎?
#coding:utf8是的,這就是因?yàn)槿绻鹥y2解釋器去執(zhí)行一個(gè)utf8編碼的文件,就會(huì)以默認(rèn)地ASCII去解碼utf8,一旦程序中有中文,自然就解碼錯(cuò)誤了,所以我們?cè)谖募_(kāi)頭位置聲明 #coding:utf8,其實(shí)就是告訴解釋器,你不要以默認(rèn)的編碼方式去解碼這個(gè)文件,而是以u(píng)tf8來(lái)解碼。而py3的解釋器因?yàn)槟J(rèn)utf8編碼,所以就方便很多了。
? ? ? ? ? ? ? ? ? ?
注意:我們上面講的string編碼是在cpu執(zhí)行程序時(shí)的存儲(chǔ)狀態(tài),是另外一個(gè)過(guò)程,不要混淆!
五 常見(jiàn)的編碼問(wèn)題
1 cmd下的亂碼問(wèn)題
hello.py
#coding:utf8 print ('苑昊')文件保存時(shí)的編碼也為utf8。
思考:為什么在IDE下用2或3執(zhí)行都沒(méi)問(wèn)題,在cmd.exe下3正確,2亂碼呢?
? ? ? 我們?cè)趙in下的終端即cmd.exe去執(zhí)行,大家注意,cmd.exe本身也一個(gè)軟件;當(dāng)我們python2 hello.py時(shí),python2解釋器(默認(rèn)ASCII編碼)去按聲明的utf8編碼文件,而文件又是utf8保存的,所以沒(méi)問(wèn)題;問(wèn)題出在當(dāng)我們print'苑昊'時(shí),解釋器這邊正常執(zhí)行,也不會(huì)報(bào)錯(cuò),只是print的內(nèi)容會(huì)傳遞給cmd.exe用來(lái)顯示,而在py2里這個(gè)內(nèi)容就是utf8編碼的字節(jié)數(shù)據(jù),可這個(gè)軟件默認(rèn)的編碼解碼方式是GBK,所以cmd.exe用GBK的解碼方式去解碼utf8自然會(huì)亂碼。
py3正確的原因是傳遞給cmd的是unicode數(shù)據(jù),cmd.exe可以識(shí)別內(nèi)容,所以顯示沒(méi)問(wèn)題。
明白原理了,修改就有很多方式,比如:
print (u'苑昊')改成這樣后,cmd下用2也不會(huì)有問(wèn)題了。
2 open()中的編碼問(wèn)題
創(chuàng)建一個(gè)hello文本,保存成utf8:
苑昊,你最帥!同目錄下創(chuàng)建一個(gè)index.py
f=open('hello') print(f.read())為什么 在linux下,結(jié)果正常:苑昊,在win下,亂碼:鑻戞槉(py3解釋器)?
因?yàn)槟愕膚in的操作系統(tǒng)安裝時(shí)是默認(rèn)的gbk編碼,而linux操作系統(tǒng)默認(rèn)的是utf8編碼;
當(dāng)執(zhí)行open函數(shù)時(shí),調(diào)用的是操作系統(tǒng)打開(kāi)文件,操作系統(tǒng)用默認(rèn)的gbk編碼去解碼utf8的文件,自然亂碼。
解決辦法:
f=open('hello',encoding='utf8') print(f.read())如果你的文件保存的是gbk編碼,在win 下就不用指定encoding了。
另外,如果你的win上不需要指定給操作系統(tǒng)encoding='utf8',那就是你安裝時(shí)就是默認(rèn)的utf8編碼或者已經(jīng)通過(guò)命令修改成了utf8編碼。
注意:open這個(gè)函數(shù)在py2里和py3中是不同的,py3中有了一個(gè)encoding=None參數(shù)。
?
轉(zhuǎn)載于:https://www.cnblogs.com/Easonlou/p/8135342.html
總結(jié)
- 上一篇: 剖析加载RAID驱动的步骤(BIOS R
- 下一篇: 电脑操作最忌讳的小动作