提及python編碼,真是句句心酸。算起來,反覆折騰兩個來月了。萬幸的是,終於梳理清楚了。做爲一個共產主義者,必定要分享給你們。若是你還在由於編碼而頭痛,那麼趕忙跟着我我們一塊兒來揭開py編碼的真相吧!html
基本概念很簡單。首先,咱們從一段信息即消息提及,消息以人類能夠理解、易懂的表示存在。我打算將這種表示稱爲「明文」(plain text)。對於說英語的人,紙張上打印的或屏幕上顯示的英文單詞都算做明文。python
其次,咱們須要能將明文表示的消息轉成另外某種表示,咱們還須要能將編碼文本轉回成明文。從明文到編碼文本的轉換稱爲「編碼」,從編碼文本又轉回成明文則爲「解碼」。linux
編碼問題是個大問題,若是不完全解決,它就會像隱藏在叢林中的小蛇,時不時地咬你一口。 那麼到底什麼是編碼呢? //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的區別。
1
|
s
=
"I'm 苑昊"
|
你看到的unicode字符集是這樣的編碼表:json
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,少了兩個,由於咱們的程序英文會遠多於中文,因此空間會提升不少!函數
記住:一切都是爲了節省你的硬盤和流量。 post
在py2中,有兩種字符串類型:str類型和unicode類型;注意,這僅僅是兩個名字,python定義的兩個名字,關鍵是這兩種數據類型在程序運行時存在內存地址的是什麼?編碼
咱們來看一下:url
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' #注意 u2=s2.decode('gbk') print u2 #鑻戞槉 print len('苑昊') #6
不管是utf8仍是gbk都只是一種編碼規則,一種把unicode數據編碼成字節數據的規則,因此utf8編碼的字節必定要用utf8的規則解碼,不然就會出現亂碼或者報錯的狀況。
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 的時候將會失敗。
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
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執行程序時的存儲狀態,是另一個過程,不要混淆!
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也不會有問題了。
建立一個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參數。