基礎內容請自行谷歌..廢話不多說,直接上乾貨!! “ 代碼 一 ”: 1 import sys, locale 2 3 s = "小甲" 4 print(s) 5 print(type(s)) 6 print(sys.getdefaultencoding()) 7 print(locale.getd ...
關於編碼的歷史演變,utf-8是如何一步步發展來的,windows為啥依舊保持gbk的編碼。。。
等等這些問題,網上一搜一大堆,大部分都是轉發、分享後的雷同內容,依舊解決不了我內心的疑惑。。。
編碼是個蛋疼的事情,倘若不弄清楚, 怎麼在中國混?
經過自己查閱多方文檔、多次深入實驗,我樹立了對編碼的基本世界觀。
基礎內容請自行谷歌..廢話不多說,直接上乾貨!!
下麵用幾個簡單的代碼段, 一步步講解編碼中“編”和“解”的問題!!(linux中運行)
“ 代碼 一 ”:
1 import sys, locale 2 3 s = "小甲" 4 print(s) 5 print(type(s)) 6 print(sys.getdefaultencoding()) 7 print(locale.getdefaultlocale()) 8 9 with open("utf1","w",encoding = "utf-8") as f: 10 f.write(s) 11 with open("gbk1","w",encoding = "gbk") as f: 12 f.write(s) 13 with open("jis1","w",encoding = "shift-jis") as f: 14 f.write(s)
代碼很簡單,學過Python的人應該都能看懂是啥意思~~
我們看一下運行結果:
“ 代碼 一 ” 運行結果:
1 小甲 2 <class 'str'> 3 utf-8 4 ('en_US', 'UTF-8')
正如大家所想, 就是將“小甲”原樣列印出來, 再把“小甲”存到3個文件中。
(shift-jis是日文編碼格式)
這麼蠢的代碼我還會不知道嗎? ( 重點在後面 )
這裡解釋一下列印出來的兩個“utf-8”是什麼意思:
上面的 utf-8 指:系統預設編碼
- 註: 不要把系統以為是操作系統,這裡可以理解成python3的編譯器本身
下麵的 utf-8 指:本地預設編碼
- 註: 這個才是操作系統的編碼。(在Windows運行會變成gbk)
現在我們分別查看utf1 、gbk1、jis1 這三個文件的內容:
utf1 : 小甲
gbk1 : С▒▒▒
jis1 : ▒▒▒b
問題:
為什麼 utf1 的內容很清楚,沒有編碼問題,而gbk1 、jis1 的內容都出現了亂碼?
解釋:
因為我文件存儲時用的編碼格式不是utf-8,而此時讀取這兩個文件時,使用的是linux操作系統的預設編碼“utf-8”。
那麼寫入磁碟時不是用utf-8, 讀出時卻用utf-8,當然讀不出來了。
(這裡需要大家瞭解encoding的真實作用)
“ 代碼 二 ”:
1 #coding=gbk 2 import sys, locale 3 4 s = "小甲" 5 #coding=gbk 6 import sys, locale 7 8 s = "小甲" 9 print(s) 10 print(type(s)) 11 print(sys.getdefaultencoding()) 12 print(locale.getdefaultlocale()) 13 14 with open("utf2","w",encoding = "utf-8") as f: 15 f.write(s) 16 with open("gbk2","w",encoding = "gbk") as f: 17 f.write(s) 18 with open("jis2","w",encoding = "shift-jis") as f: 19 f.write(s)
代碼結結構一樣很簡單
但是請大家註意: 我在頭部加了某個編碼聲明
在代碼運行前, 請大家自行猜測結果~~~
“ 代碼 二 ” 運行結果 :
1 灝忕敳 2 <class 'str'> 3 utf-8 4 ('en_US', 'UTF-8') 5 Traceback (most recent call last): 6 File "2", line 15, in <module> 7 f.write(s) 8 UnicodeEncodeError: 'shift_jis' codec can't encode character '\u704f' in position 0: illegal multibyte sequence
問題來了:
1、代碼中明明 s = “小甲”, 為什麼變成了 “灝忕敳” ??
2、為什麼 jis 的編碼失敗了?(之前頂多只出現了亂碼的問題,還不會報錯,那它內部到底發生了什麼?)
3、“coding=gbk” 到底是什麼意思??
4、我明明寫了 “coding=gbk” 的編碼聲明,為什麼系統編碼、本地預設編碼還是沒有改變?(那我寫了有啥用?)
解釋一下:
以上這麼多問題, 主要是因為沒搞清楚頭文件的 “coding=gbk” 編碼聲明是什麼意思!!
1、它的意思是python3編譯器在讀取該.py文件時候,我應該用什麼格式將它 “解碼”?只和讀取有關,所以當你確定你代碼編輯時候用的是什麼格式編碼的,你才能把相應的編碼格式寫入頭文件。
(在此示範代碼中,我用的是linux的預設編碼編輯,也就是utf-8,那麼在後面運行的時候,卻要求解釋器用gbk去解碼,自然很過分,就會出現了s=“小甲” 亂碼的問題)
(大家一定要知道,編碼是 “編” 和 “解” 的兩個步驟,一定要一一對應才能正確解碼!雖然通常我們都叫“編碼格式”,這是有一定誤導性的。
實際上另一半是“解碼格式”,要有意識地區分 “編” 和 “解” ,我們不能像網上有些文章一樣將這兩者混為一談!!)
2、根據上面的解釋應該可以明白,寫了它之後,並不會更改本地、系統預設編碼。
(本地預設編碼只跟操作系統相關,linux中是utf-8,windows中是gbk。)
(系統預設編碼實際是有python3和python2的差異的,python3是utf-8,python2是ascii。)
3、那麼,上面兩種編碼的作用體現在哪裡呢?
敲黑板,劃重點:
系統預設編碼 指:
在python3編譯器讀取.py文件時,若沒有頭文件編碼聲明,則預設使用“utf-8”來對.py文件進行解碼。並且在調用 encode()這個函數時,不傳參的話預設是“ utf-8 ”。(這與下麵的open( )函數中的“encoding”參數要做區分,非常誤導人!!!)
本地預設編碼 指:
在你編寫的python3程式時,若使用了 open( )函數 ,而不給它傳入 “ encoding ” 這個參數,那麼會自動使用本地預設編碼。沒錯,如果在Windows系統中,就是預設用gbk格式!!!
(這個問題困擾了我好久, 不說好了一直預設utf-8到天長地久的嘛,咋換成win後就頻頻失信呢。所以請大家在這裡註意:linux中可以不用傳“ encoding” 的參數, 而win中不能忘了~~~)
4、再來回答一下報錯的問題:
因為我們的編譯器已經用了gbk來解碼此.py文件了,所以讀取出來的變數 s 已經變成了我們現在看到的“ 灝忕敳 ” 了!那麼此時把 s 存到磁碟文件中,實際上存的是亂碼後的 “ 灝忕敳 ”。而在日文中,是沒有這3個字的, 所以自然反饋說 “ 在 position 0 的位置,編碼失敗了”
現在我們再來分別查看utf2 、gbk2、jis2 這三個文件的內容:
utf2 : 灝忕敳
gbk2 : 小甲
jis2 :
(跟你想象中的結果是否一樣呢??嘿嘿嘿~~)
問題:
1、為什麼 我用 “utf-8 ” 去編碼存儲,後來用linux預設的 “ utf-8 ” 去解碼,卻出現亂碼?
2、為什麼我用“ gbk ” 去編碼存儲, 後面用linux預設的 “ utf-8 ” 去解碼,明明編碼、解碼格式不一致,卻能夠正常顯示?
解釋:
1、實際上面兩個問題是同一個問題,相信細心的同學已經知道問題出在哪裡了,我上文已經說的很清楚了。此時的變數 s 已經變成了“ 灝忕敳 ”, 那麼utf2這個文本文件自然是顯示“灝忕敳”。
2、而“灝忕敳”這三個字元是怎麼來的呢?
第1步: 小甲(unicode) ---用 "utf-8" 編碼---> e5b0 8fe7 94b2 (utf-8編碼後的二進位代碼)
第2步: e5b0 8fe7 94b2 ---用 “gbk” 解碼---> " 灝忕敳 " (unicode)(亂碼)
第3步: “ 灝忕敳 ” --- 用 “ gbk ” 編碼---> e5b0 8fe7 94b2 ( 第2步的逆向)
第4步: e5b0 8fe7 94b2 ---用 “ utf-8 ” 解碼---> 小甲(unicode)
我想上述的步驟夠清楚了吧 ~
第3、 4 步就是逆推回去,就變成了正常的 “ 小甲 ”
看懂了這個 “ 編碼 ” 和 “ 解碼 ” 的過程,你的編碼問題已經解決大半了!
“ 代碼 三 ”:
#coding=shift-jis import sys, locale s = "小甲" print(s) print(type(s)) print(sys.getdefaultencoding()) print(locale.getdefaultlocale(), "\n\n") a = s.encode("shift-jis") print(a) print(type(a)) b = a.decode("utf-8") print(b) print(type(b)) print(a.decode("gbk")) with open("utf3","w",encoding = "utf-8") as f: f.write(s) with open("gbk3","w",encoding = "gbk") as f: f.write(s) with open("jis3","w",encoding = "shift-jis") as f: f.write(s) #Python學習群548377875
代碼整體結構還是老樣子,只不過中間多加了一小段代碼,便於解釋~
“ 代碼 三 ” 運行結果 :
蟆冗抜 <class 'str'> utf-8 ('en_US', 'UTF-8') b'\xe5\xb0\x8f\xe7\x94\xb2' <class 'bytes'> 小甲 <class 'str'> 灝忕敳
這裡可以看到,此時我們的變數 s 已經變成了“ 蟆冗抜 ”(另一個用jis解碼造成的亂碼)。
那麼此時,我把 “ 蟆冗抜 ” 用 “ shift-jis ” 解碼回去並賦值給變數 a,列印一下,可以看到 a 就是正常顯示的 “ 小甲 ”, 這也證明瞭我上面的推斷是絕對正確的!!
現在,我們依舊分別查看一下 utf3 、gbk3、jis3 這三個文件的內容:
utf3 : 蟆冗抜
gbk3 : ▒▒ߒi
jis3 : 小甲
(oops~~ 見鬼,又是這麼亂七八糟的東西)
這裡我澄清一下,實際上utf3這個至少還能有文字,這叫亂碼。而gbk3那個東西一團黑是什麼鬼,是報錯,linux的預設編碼無法解碼gbk3的文件,所以列印地亂七八糟。
問題:
- 為什麼 utf3 的文件是顯示亂碼, 而 gbk3 的文件卻是報錯呢??
解釋:
- 這是因為 utf-8 與 gbk 編碼的演算法差異。
- 我們最常看到的是utf-8解碼報錯,因為它是可變長的的編碼,有1個位元組的英文字元,也有2個位元組的阿拉伯文,也有3個位元組的中文和日文。
- gbk對英文是使用單位元組編碼(也就意味著相容ascii),而gbk對中文部分是採取定長的2位元組,總體編碼範圍為 8140-FEFE,首位元組在 81-FE 之間,尾位元組在 40-FE 之間。所以說它只要沒有碰到尾位元組在40之內的字元,都會一股腦地按照2位元組去解碼成中文。而中文在 utf-8 編碼後,一般是三位元組的。當解碼的位元組數和編碼的位元組數不匹配時,自然會造成全是亂碼的局面。
(此處感謝 “流放的國王” 的指正~~)
- 而utf-8是有嚴格定義的,一個位元組的字元高位必須是0;三個位元組的字元中,第一個位元組的高位是1110開頭。
- (相關utf-8的編碼演算法鏈接)
至此,代碼的示範部分就結束了~~ 碼字碼得我手酸 ~~~~(>_<)~~~~
最後,
tips:
1、所有文件的編碼格式都由你當下使用的編輯器決定的!!在windows中編輯的文本放在瀏覽器解析顯示的時候,有時亂碼,有時又正常,這是由於windows中很多文本編輯器預設使用和操作系統一致的編碼格式。
所以在文本存儲前,一定要搞清楚我們用的是utf-8還是gbk!!!
而當你使用Python的 open( ) 函數時,是記憶體中的進程與磁碟的交互,而這個交互過程中的編碼格式則是使用操作系統的預設編碼(Linux為utf-8,windows為gbk)
2、相信學Python的同學們經常會聽到,python3 的預設編碼是utf-8。而有的時候,又有人說python3 的預設編碼是unicode,那麼是不是會有人跟我初學時候一樣傻傻分不清楚這兩者的關係呢?
- 實際上unicode就是一個字元集,一個字元與數字一一對應的映射關係,因為它一律以2個位元組編碼(或者也有4個位元組的,這裡不討論),所以占用空間會大一些,一般只用於記憶體中的編碼使用。
- 而 utf-8 是為了實現unicode 的傳輸和存儲的。因為它可變長,存英文時候可以節省大量存儲空間。傳輸時候也節省流量,所以更加 “ international ”~
所以說,上述兩種說法沒有歧義,進程在記憶體中的表現是“ unicode ”的編碼;當python3編譯器讀取磁碟上的.py文件時,是預設使用“utf-8”的;當進程中出現open(), write() 這樣的存儲代碼時,需要與磁碟進行存儲交互時,則是預設使用操作系統的預設編碼。
我也不知道如何才能成為一條 “ 華麗 ” 的分割線~~~
打字、排版、整理思路花了近5個小時,若是這篇文章有幫助到你、有給你帶來一些對編碼的新靈感,希望可以點個贊。
比心~ ❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤