1. 程式人生 > >吐血總結,徹底明白 python3 編碼原理

吐血總結,徹底明白 python3 編碼原理

 

關於編碼的歷史演變,utf-8是如何一步步發展來的,windows為啥依舊保持gbk的編碼。。。
等等這些問題,網上一搜一大堆,大部分都是轉發、分享後的雷同內容,依舊解決不了我內心的疑惑。。。
編碼是個蛋疼的事情,倘若不弄清楚, 怎麼在中國混?
經過自己查閱多方文件、多次深入實驗,我樹立了對編碼的基本世界觀。                                                                                           

基礎內容請自行谷歌..廢話不多說,直接上乾貨!!

下面用幾個簡單的程式碼段, 一步步講解編碼中 “編”“解”的問題!!(linux中執行)                                                                      

 


“ 程式碼 一 ”:

import sys, locale

s = "小甲"
print(s)
print(type(s))
print(sys.getdefaultencoding())
print(locale.getdefaultlocale())

with open("utf1","w",encoding = "utf-8") as f:
    f.write(s)
with open("gbk1","w",encoding = "gbk") as f:
    f.write(s)
with open("jis1","w",encoding = "shift-jis") as f:
    f.write(s)
程式碼很 簡單,學過Python的人應該都能看懂是啥意思~~
我們看一下執行結果:

“ 程式碼 一 ” 執行結果:

小甲
<class 'str'>
utf-8
('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的真實作用)                                                                                                                                     

 

“ 程式碼 二 ”:

#coding=gbk
import sys, locale

s = "小甲"
#coding=gbk
import sys, locale

s = "小甲"
print(s)
print(type(s))
print(sys.getdefaultencoding())
print(locale.getdefaultlocale())

with open("utf2","w",encoding = "utf-8") as f:
    f.write(s)
with open("gbk2","w",encoding = "gbk") as f:
    f.write(s)
with open("jis2","w",encoding = "shift-jis") as f:
    f.write(s)
程式碼結結構一樣很簡單
但是請大家注意:  我在頭部加了某個編碼宣告
在程式碼執行前, 請大家自行猜測結果~~~                                                                                                                                                                                                                                                                

“ 程式碼 二 ” 執行結果 :

灝忕敳
<class 'str'>
utf-8
('en_US', 'UTF-8')
Traceback (most recent call last):
  File "2", line 15, in <module>
    f.write(s)
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)
程式碼整體結構還是老樣子,只不過中間多加了一小段程式碼,便於解釋~                                                                                                                                

“ 程式碼 三 ” 執行結果 :

蟆冗抜
<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是定長的2位元組,比較死板,utf-8編出來的二進位制檔案,它常常也會一股腦的全部按照2個位元組、2個位元組地去解碼,結果可想而知,全是亂碼!!!
  • 而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個小時,若是這篇文章有幫助到你、有給你帶來一些對編碼的新靈感,希望可以點個贊。

第一次寫文章,只是想得到一點點認可,哈哈哈~~~

比心~ ❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤

:-D

如果覺得排版有點看不清楚戳這裡,閱讀原文噢~~

 

 

若還有對編碼的任何問題,歡迎給我留言!!!

若是我哪裡寫的不對,也請斧正,咱們一起探討哦,哈哈哈~~ 
\@>_<@~