天天看點

python的編碼格式問題

由于每個國家都有自己的字元,是以其對應關系也涵蓋了自己國家的字元

  • ASCII 占1個位元組,隻支援英文
  • GB2312 占2個位元組,支援6700+漢字
  • GBK GB2312的更新版,支援21000+漢字
  • Shift-JIS 日本字元
  • ks_c_5601-1987 南韓編碼
  • TIS-620 泰國編碼

但是以上編碼都存在局限性,即:僅涵蓋本國字元,無其他國家字元的對應關系。應運而生出現了萬國碼,他涵蓋了全球所有的文字和二進制的對應關系,

  • Unicode 2-4位元組 已經收錄136690個字元,并還在一直不斷擴張中…

Unicode 起到了2個作用:

  • 直接支援全球所有語言,每個國家都可以不用再使用自己之前的舊編碼了,用unicode就可以了。(就跟英語是全球統一語言一樣)
  • unicode包含了跟全球所有國家編碼的映射關系。

Unicode解決了字元和二進制的對應關系,但是使用unicode表示一個字元,太浪費空間。例如:利用unicode表示“Python”需要12個位元組才能表示,比原來ASCII表示增加了1倍。

由于計算機的記憶體比較大,并且字元串在内容中表示時也不會特别大,是以内容可以使用unicode來處理,但是存儲和網絡傳輸時一般資料都會非常多,那麼增加1倍将是無法容忍的!!!

為了解決存儲和網絡傳輸的問題,出現了Unicode Transformation Format,學術名UTF,即:對unicode中的進行轉換,以便于在存儲和網絡傳輸時可以節省空間!

  • UTF-8: 使用1、2、3、4個位元組表示所有字元;優先使用1個字元、無法滿足則使增加一個位元組,最多4個位元組。英文占1個位元組、歐洲語系占2個、東亞占3個,其它及特殊字元占4個
  • UTF-16: 使用2、4個位元組表示所有字元;優先使用2個位元組,否則使用4個位元組表示
  • UTF-32: 使用4個位元組表示所有字元;

總結:UTF 是為unicode編碼 設計 的一種 在存儲 和傳輸時節省空間的編碼方案。

字元在硬碟上的存儲

無論以什麼編碼在記憶體裡顯示字元,存到硬碟上都是2進制。

ascii編碼(美國):
    l   0b1101100
    o   0b1101111
    v   0b1110110
    e   0b1100101
GBK編碼(中國):
    中   0b11000000 0b11001111
    國   0b11000100 0b11010000
    人   0b10111010 0b10100010

Shift_JIS編碼(日本):
    私   0b10001110 0b10000100
    は   0b10000010 0b11001101

ks_c_5601-編碼(南韓):
    나   0b10110011 0b10101010
    는   0b10110100 0b11000010

TIS-編碼(泰國):
    ฉัน  0b10101001 0b11010001 0b10111001
...
####要注意的是,存到硬碟上時是以何種編碼存的,再從硬碟上讀出來時,就必須以何種編碼讀,要不然就亂了。。
           

編碼的轉換

雖然國際語言是英語 ,但大家在自己的國家依然說自已的語言,不過出了國, 你就得會英語

編碼也一樣,雖然有了unicode and utf-8 , 但是由于曆史問題,各個國家依然在大量使用自己的編碼,比如中國的windows,預設編碼依然是gbk,而不是utf-8

基于此,如果中國的軟體出口到美國,在美國人的電腦上就會顯示亂碼,因為他們沒有gbk編碼。

若想讓中國的軟體可以正常的在 美國人的電腦上顯示,隻有以下2條路可走:

  • 讓美國人的電腦上都裝上gbk編碼
  • 把你的軟體編碼以utf-8編碼

第1種方法幾乎不可能實作,第2種方法比較簡單。 但是也隻能是針對新開發的軟體。 如果你之前開發的軟體就是以gbk編碼的,上百萬行代碼可能已經寫出去了,重新編碼成utf-8格式也會費很大力氣。

so , 針對已經用gbk開發完畢的項目,以上2種方案都不能輕松的讓項目在美國人電腦上正常顯示,難道沒有别的辦法了麼?

有, 還記得我們講unicode其中一個功能是其包含了跟全球所有國家編碼的映射關系,意思就是,你寫的是gbk的“路飛學城”,但是unicode能自動知道它在unicode中的“路飛學城”的編碼是什麼,如果這樣的話,那是不是意味着,無論你以什麼編碼存儲的資料 ,隻要你的軟體在把資料從硬碟讀到記憶體裡,轉成unicode來顯示,就可以了。

由于所有的系統、程式設計語言都預設支援unicode,那你的gbk軟體放到美國電腦 上,加載到記憶體裡,變成了unicode,中文就可以正常展示啦。

unicode與gbk的映射表 http://www.unicode.org/charts/

python的編碼格式問題

python3的執行過程

在看實際代碼的例子前,我們來聊聊,python3 執行代碼的過程

解釋器找到代碼檔案,把代碼字元串按檔案頭定義的編碼加載到記憶體,轉成unicode
把代碼字元串按照文法規則進行解釋,
所有的變量字元都會以unicode編碼聲明
           

編碼轉換過程

代碼示範如下,在py3上 把你的代碼以utf-8編寫, 儲存,然後在windows上執行,

s = '中國'
print(s)
           
python的編碼格式問題

so ,一切都很美好,到這裡,我們關于編碼的學習按說就可以結束了。

是,如生活一樣,美好的表面下,總是隐藏着不盡如人意,上面的utf-8編碼之是以能在windows gbk的終端下顯示正常,是因為到了記憶體裡python解釋器把utf-8轉成了unicode , 但是這隻是python3, 并不是所有的程式設計語言在記憶體裡預設編碼都是unicode,比如 萬惡的python2 就不是, 它的預設編碼是ASCII,想寫中文,就必須聲明檔案頭的coding為gbk or utf-8, 聲明之後,python2解釋器僅以檔案頭聲明的編碼去解釋你的代碼,加載到記憶體後,并不會主動幫你轉為unicode,也就是說,你的檔案編碼是utf-8,加載到記憶體裡,你的變量字元串就也是utf-8, 這意味着什麼你知道麼?。。。意味着,你以utf-8編碼的檔案,在windows是亂碼。

在python2中執行上述代碼:

python的編碼格式問題

再加上編碼聲明後如下代碼:

#_*_coding:utf8_*_
s = '中國'
print(s)
           
python的編碼格式問題

亂是正常的,不亂才不正常,因為隻有2種情況 ,你的windows上顯示才不會亂

字元串以GBK格式顯示
字元串是unicode編碼
           

既然Python2并不會自動的把檔案編碼轉為unicode存在記憶體裡, 那就隻能使出最後一招了,你自己人肉轉。Py3 自動把檔案編碼轉為unicode必定是調用了什麼方法,這個方法就是,decode(解碼) 和encode(編碼)

UTF-8 --> decode 解碼 --> Unicode
Unicode --> encode 編碼 --> GBK / UTF-8 .. 
           

decode示例:

#_*_coding:utf8_*_
s = '中國'
print type(s)
print s

s2 = s.decode('utf8_')
print type(s2)
print s2

s3 = s2.encode('GBK')
print type(s3)
print s3
           
python的編碼格式問題

記住下圖規則:

python的編碼格式問題

Python bytes類型

在python 2 上寫字元串

>>> s = "中國"
>>> print s
中國
>>> s
'\xe8\xb7\xaf\xe9\xa3\x9e'
           

雖說列印的是中國,但直接調用變量s,看到的卻是一個個的16進制表示的二進制位元組,我們怎麼稱呼這樣的資料呢?直接叫二進制麼?也可以, 但相比于010101,這個資料串在表示形式上又把2進制轉成了16進制來表示,這是為什麼呢? 哈,為的就是讓人們看起來更可讀。我們稱之為bytes類型,即位元組類型, 它把8個二進制一組稱為一個byte,用16進制來表示。  

說這個有什麼意思呢?

想告訴你一個事實, 就是,python2的字元串其實更應該稱為位元組串。 通過存儲方式就能看出來,但python2裡還有一個類型是bytes呀,難道又叫bytes又叫字元串? 嗯 ,是的,在python2裡,bytes == str , 其實就是一回事 

除此之外呢, python2裡還有個單獨的類型是unicode , 把字元串解碼後,就會變成unicode

>>> s
'\xe8\xb7\xaf\xe9\xa3\x9e' #utf-8
>>> s.decode('utf-8')
u'\u8def\u98de' #unicode 在unicode編碼表裡對應的位置
>>> print(s.decode('utf-8'))
 #unicode 格式的字元
           

由于Python創始人在開發初期認知的局限性,其并未預料到python能發展成一個全球流行的語言,導緻其開發初期并沒有把支援全球各國語言當做重要的事情來做,是以就輕佻的把ASCII當做了預設編碼。 當後來大家對支援漢字、日文、法語等語言的呼聲越來越高時,Python于是準備引入unicode,但若直接把預設編碼改成unicode的話是不現實的, 因為很多軟體就是基于之前的預設編碼ASCII開發的,編碼一換,那些軟體的編碼就都亂了。是以Python 2 就直接 搞了一個新的字元類型,就叫unicode類型,比如你想讓你的中文在全球所有電腦上正常顯示,在記憶體裡就得把字元串存成unicode類型

>>> s = "中國"
>>> s
'\xe8\xb7\xaf\xe9\xa3\x9e'
>>> s2 = s.decode("utf-8")
>>> s2
u'\u8def\u98de'
>>> type(s2)
<type 'unicode'>
           

時間來到2008年,python發展已近20年,創始人龜叔越來越覺得python裡的好多東西已發展的不像他的初衷那樣,開始變得臃腫、不簡潔、且有些設計讓人摸不到頭腦,比如unicode 與str類型,str 與bytes類型的關系,這給很多python程式員造成了困擾。

龜叔再也忍不了,像之前一樣的修修補補已不能讓Python變的更好,于是來了個大變革,Python3橫空出世,不相容python2,python3比python2做了非常多的改進,其中一個就是終于把字元串變成了unicode,檔案預設編碼變成了utf-8,這意味着,隻要用python3,無論你的程式是以哪種編碼開發的,都可以在全球各國電腦上正常顯示,真是太棒啦!

PY3 除了把字元串的編碼改成了unicode, 還把str 和bytes 做了明确區分, str 就是unicode格式的字元, bytes就是單純二進制啦。

最後一個問題,為什麼在py3裡,把unicode編碼後,字元串就變成了bytes格式? 你直接給我直接列印成gbk的字元展示不好麼?我想其實py3的設計真是煞費苦心,就是想通過這樣的方式明确的告訴你,想在py3裡看字元,必須得是unicode編碼,其它編碼一律按bytes格式展示。

最後再提示一下,Python隻要出現各種編碼問題,無非是哪裡的編碼設定出錯了

常見編碼錯誤的原因有:

  • Python解釋器的預設編碼
  • Python源檔案檔案編碼
  • Terminal使用的編碼
  • 作業系統的語言設定

掌握了編碼之前的關系後,挨個排錯就好啦