python編碼終極版
說起python編碼,真是句句心酸。算起來,反覆折騰兩個來月了。萬幸的是,終於梳理清楚了。作為一個共產主義者,一定要分享給大家。如果你還在因為編碼而頭痛,那麼趕緊跟著我咱們一起來揭開py編碼的真相吧!
一 什麼是編碼?
基本概念很簡單。首先,我們從一段資訊即訊息說起,訊息以人類可以理解、易懂的表示存在。我打算將這種表示稱為“明文”(plain text)。對於說英語的人,紙張上列印的或螢幕上顯示的英文單詞都算作明文。
其次,我們需要能將明文表示的訊息轉成另外某種表示,我們還需要能將編碼文字轉回成明文。從明文到編碼文字的轉換稱為“編碼”,從編碼文字又轉回成明文則為“解碼”。
編碼問題是個大問題,如果不徹底解決,它就會像隱藏在叢林中的小蛇,時不時地咬你一口。 那麼到底什麼是編碼呢? //ASCII 記住一句話:計算機中的所有資料,不論是文字、圖片、視訊、還是音訊檔案,本質上最終都是按照類似 01010101 的二進位制儲存的。
再說簡單點,計算機只懂二進位制數字! 所以,目的明確了:如何將我們能識別的符號唯一的與一組二進位制數字對應上?於是美利堅的同志想到通過一個電平的高低狀態來代指0或1,
八個電平做為一組就可以表示出 256種不同狀態,每種狀態就唯一對應一個字元,比如A--->00010001,而英文只有26個字元,算上一些特殊字元和數字,128個狀態也夠
用了;每個電平稱為一個位元為,約定8個位元位構成一個位元組,這樣計算機就可以用127個不同位元組來儲存英語的文字了。這就是ASCII編碼。 擴充套件ANSI編碼 剛才說了,最開始,一個位元組有八位,但是最高位沒用上,預設為0;後來為了計算機也可以表示拉丁文,就將最後一位也用上了, 從128到255的字符集對應拉丁文啦。至此,一個位元組就用滿了! //GB2312 計算機漂洋過海來到中國後,問題來了,計算機不認識中文,當然也沒法顯示中文;而且一個位元組所有狀態都被佔滿了,萬惡的帝國主義亡
我之心不死啊!我黨也是棒,自力更生,自己重寫一張表,直接生猛地將擴充套件的第八位對應拉丁文全部刪掉,規定一個小於127的字元的意
義與原來相同,但兩個大於127的字元連在一起時,就表示一個漢字,前面的一個位元組(他稱之為高位元組)從0xA1用到0xF7,後面一個位元組
(低位元組)從0xA1到0xFE,這樣我們就可以組合出大約7000多個簡體漢字了;這種漢字方案叫做 “GB2312”。GB2312 是對 ASCII 的中文擴充套件。 //GBK 和 GB18030編碼 但是漢字太多了,GB2312也不夠用,於是規定:只要第一個位元組是大於127就固定表示這是一個漢字的開始,不管後面跟的是不是擴充套件字符集裡的
內容。結果擴充套件之後的編碼方案被稱為 GBK 標準,GBK 包括了 GB2312 的所有內容,同時又增加了近20000個新的漢字(包括繁體字)和符號。 //UNICODE編碼: 很多其它國家都搞出自己的編碼標準,彼此間卻相互不支援。這就帶來了很多問題。於是,國際標誰化組織為了統一編碼:提出了標準編碼準
則:UNICODE 。 UNICODE是用兩個位元組來表示為一個字元,它總共可以組合出65535不同的字元,這足以覆蓋世界上所有符號(包括甲骨文) //utf8: unicode都一統天下了,為什麼還要有一個utf8的編碼呢? 大家想,對於英文世界的人們來講,一個位元組完全夠了,比如要儲存A,本來00010001就可以了,現在吃上了unicode的大鍋飯, 得用兩個位元組:00000000 00010001才行,浪費太嚴重! 基於此,美利堅的科學家們提出了天才的想法:utf8. UTF-8(8-bit Unicode Transformation Format)是一種針對Unicode的可變長度字元編碼,它可以使用1~4個位元組表示一個符號,根據
不同的符號而變化位元組長度,當字元在ASCII碼的範圍時,就用一個位元組表示,所以是相容ASCII編碼的。 這樣顯著的好處是,雖然在我們記憶體中的資料都是unicode,但當資料要儲存到磁碟或者用於網路傳輸時,直接使用unicode就遠不如utf8省空間啦! 這也是為什麼utf8是我們的推薦編碼方式。 Unicode與utf8的關係: 一言以蔽之:Unicode是記憶體編碼表示方案(是規範),而UTF是如何儲存和傳輸Unicode的方案(是實現)這也是UTF與Unicode的區別。
補充:utf8是如何節約硬碟和流量的
1 |
s = "I'm 苑昊"
|
你看到的unicode字符集是這樣的編碼表:
I 0049 ' 0027 m 006d 0020 苑 82d1 昊 660a
每一個字元對應一個十六進位制數字。
計算機只懂二進位制,因此,嚴格按照unicode的方式(UCS-2),應該這樣儲存:
I 00000000 01001001 ' 00000000 00100111 m 00000000 01101101 00000000 00100000 苑 10000010 11010001 昊 01100110 00001010
這個字串總共佔用了12個位元組,但是對比中英文的二進位制碼,可以發現,英文前9位都是0!浪費啊,浪費硬碟,浪費流量。怎麼辦?UTF8:
I 01001001 ' 00100111 m 01101101 00100000 苑 11101000 10001011 10010001 昊 11100110 10011000 10001010
utf8用了10個位元組,對比unicode,少了兩個,因為我們的程式英文會遠多於中文,所以空間會提高很多!
記住:一切都是為了節省你的硬碟和流量。
二 py2的string編碼
在py2中,有兩種字串型別:str型別和unicode型別;注意,這僅僅是兩個名字,python定義的兩個名字,關鍵是這兩種資料型別在程式執行時存在記憶體地址的是什麼?
我們來看一下:
1 2 3 4 5 6 7 8 9 10 |
#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'
|
內建函式repr可以幫我們在這裡顯示儲存內容。原來,str和unicode分別存的是位元組資料和unicode資料;那麼兩種資料之間是什麼關心呢?如何轉換呢?這裡就涉及到編碼(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’
無論是utf8還是gbk都只是一種編碼規則,一種把unicode資料編碼成位元組資料的規則,所以utf8編碼的位元組一定要用utf8的規則解碼,否則就會出現亂碼或者報錯的情況。
py2編碼的特色:
1 2 3 4 5 6 7 8 9 |
#coding:utf8
print '苑昊' # 苑昊
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 的轉換,只要資料全部是 ASCII 的話,所有的轉換都是正確的,一旦一個非 ASCII 字元偷偷進入你的程式,那麼預設的解碼將會失效,從而造成 UnicodeDecodeError 的錯誤。py2編碼讓程式在處理 ASCII 的時候更加簡單。你復出的代價就是在處理非 ASCII 的時候將會失敗。
三 py3的string編碼
python3 renamed the unicode type to str ,the old str type has been replaced by bytes.
py3也有兩種資料型別:str和bytes; str型別存unicode資料,bytse型別存bytes資料,與py2比只是換了一下名字而已。
import json s='苑昊' 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的編碼哲學:
Python 3最重要的新特性大概要算是對文字和二進位制資料作了更為清晰的區分,不再會對bytes位元組串進行自動解碼。文字總是Unicode,由str型別表示,二進位制資料則由bytes型別表示。Python 3不會以任意隱式的方式混用str和bytes,正是這使得兩者的區分特別清晰。你不能拼接字串和位元組包,也無法在位元組包裡搜尋字串(反之亦然),也不能將字串傳入引數為位元組包的函式(反之亦然)。
1 2 |
#print('alvin'+u'yuan')#位元組串和unicode連線 py2:alvinyuan
print (b 'alvin' + 'yuan' ) #位元組串和unicode連線 py3:報錯 can't concat bytes to str
|
注意:無論py2,還是py3,與明文直接對應的就是unicode資料,列印unicode資料就會顯示相應的明文(包括英文和中文)
四 檔案從磁碟到記憶體的編碼(******)
說到這,才來到我們的重點!
拋開執行執行程式,請問大家,文字編輯器大家都是用過吧,如果不懂是什麼,那麼word總用過吧,ok,當我們在word上編輯文字的時候,不管是中文還是英文,計算機都是不認識的,那麼在儲存之前資料是通過什麼形式存在記憶體的呢?yes,就是unicode資料,為什麼要存unicode資料,這是因為它的名字最屌:萬國碼!解釋起來就是無論英文,中文,日文,拉丁文,世界上的任何字元它都有唯一編碼對應,所以相容性是最好的。
好,那當我們儲存了存到磁碟上的資料又是什麼呢?
答案是通過某種編碼方式編碼的bytes位元組串。比如utf8---一種可變長編碼,很好的節省了空間;當然還有歷史產物的gbk編碼等等。於是,在我們的文字編輯器軟體都有預設的儲存檔案的編碼方式,比如utf8,比如gbk。當我們點選儲存的時候,這些編輯軟體已經”默默地”幫我們做了編碼工作。
那當我們再開啟這個檔案時,軟體又默默地給我們做了解碼的工作,將資料再解碼成unicode,然後就可以呈現明文給使用者了!所以,unicode是離使用者更近的資料,bytes是離計算機更近的資料。
說了這麼多,和我們程式執行有什麼關係呢?
先明確一個概念:py直譯器本身就是一個軟體,一個類似於文字編輯器一樣的軟體!
現在讓我們一起還原一個py檔案從建立到執行的編碼過程:
開啟pycharm,建立hello.py檔案,寫入
ret=1+1 s='苑昊' print(s)
當我們儲存的的時候,hello.py檔案就以pycharm預設的編碼方式儲存到了磁碟;關閉檔案後再開啟,pycharm就再以預設的編碼方式對該檔案開啟後讀到的內容進行解碼,轉成unicode到記憶體我們就看到了我們的明文;
而如果我們點選執行按鈕或者在命令列執行該檔案時,py直譯器這個軟體就會被呼叫,開啟檔案,然後解碼存在磁碟上的bytes資料成unicode資料,這個過程和編輯器是一樣的,不同的是直譯器會再將這些unicode資料翻譯成C程式碼再轉成二進位制的資料流,最後通過控制作業系統呼叫cpu來執行這些二進位制資料,整個過程才算結束。
那麼問題來了,我們的文字編輯器有自己預設的編碼解碼方式,我們的直譯器有嗎?
當然有啦,py2預設ASCII碼,py3預設的utf8,可以通過如下方式查詢
1 2 |
import sys
print (sys.getdefaultencoding())
|
大家還記得這個宣告嗎?
1 |
#coding:utf8
|
是的,這就是因為如果py2直譯器去執行一個utf8編碼的檔案,就會以預設地ASCII去解碼utf8,一旦程式中有中文,自然就解碼錯誤了,所以我們在檔案開頭位置宣告 #coding:utf8,其實就是告訴直譯器,你不要以預設的編碼方式去解碼這個檔案,而是以utf8來解碼。而py3的直譯器因為預設utf8編碼,所以就方便很多了。
注意:我們上面講的string編碼是在cpu執行程式時的儲存狀態,是另外一個過程,不要混淆!
五 常見的編碼問題
1 cmd下的亂碼問題
hello.py
1 2 |
#coding:utf8
print ( '苑昊' )
|
檔案儲存時的編碼也為utf8。
思考:為什麼在IDE下用2或3執行都沒問題,在cmd.exe下3正確,2亂碼呢?
我們在win下的終端即cmd.exe去執行,大家注意,cmd.exe本身也一個軟體;當我們python2 hello.py時,python2直譯器(預設ASCII編碼)去按宣告的utf8編碼檔案,而檔案又是utf8儲存的,所以沒問題;問題出在當我們print’苑昊’時,直譯器這邊正常執行,也不會報錯,只是print的內容會傳遞給cmd.exe用來顯示,而在py2裡這個內容就是utf8編碼的位元組資料,可這個軟體預設的編碼解碼方式是GBK,所以cmd.exe用GBK的解碼方式去解碼utf8自然會亂碼。
py3正確的原因是傳遞給cmd的是unicode資料,cmd.exe可以識別內容,所以顯示沒問題。
明白原理了,修改就有很多方式,比如:
1 |
print (u '苑昊' )
|
改成這樣後,cmd下用2也不會有問題了。
2 open()中的編碼問題
建立一個hello文字,儲存成utf8:
苑昊,你最帥!
同目錄下建立一個index.py
f=open('hello') print(f.read())
為什麼 在linux下,結果正常:苑昊,在win下,亂碼:鑻戞槉(py3直譯器)?
因為你的win的作業系統安裝時是預設的gbk編碼,而linux作業系統預設的是utf8編碼;
當執行open函式時,呼叫的是作業系統開啟檔案,作業系統用預設的gbk編碼去解碼utf8的檔案,自然亂碼。
解決辦法:
f=open('hello',encoding='utf8') print(f.read())
如果你的檔案儲存的是gbk編碼,在win 下就不用指定encoding了。
另外,如果你的win上不需要指定給作業系統encoding=’utf8’,那就是你安裝時就是預設的utf8編碼或者已經通過命令修改成了utf8編碼。
注意:open這個函式在py2裡和py3中是不同的,py3中有了一個encoding=None引數。