首頁 > 軟體

python編碼問題彙總

2022-03-23 13:02:16

一、瞭解字元編碼的知識儲備

1. 文字編輯器存取檔案的原理(nodepad++,pycharm,word)

 開啟編輯器就開啟了啟動了一個程序,是在記憶體中的,所以在編輯器編寫的內容也都是存放與記憶體中的,斷電後資料丟失,因而需要儲存到硬碟上,點選儲存按鈕,就從記憶體中把資料刷到了硬碟上。在這一點上,我們編寫一個py檔案(沒有執行),跟編寫其他檔案沒有任何區別,都只是在編寫一堆字元而已。

即:在沒有點選儲存時,我們所寫的內容都是寫入記憶體。注意這一點,很重要!!當我們點選儲存,內容才被刷到硬碟。

上面做了兩件事:寫內容到記憶體,從記憶體將記憶體刷到硬碟。這是兩個過程。

2. python直譯器執行py檔案的原理 

例如:python test.py

  • 第一階段:python直譯器啟動,此時就相當於啟動了一個文字編輯器
  • 第二階段:python直譯器相當於文字編輯器,去開啟test.py檔案,從硬碟上將test.py的檔案內容讀入到記憶體中
  • 第三階段:python直譯器解釋執行剛剛載入到記憶體中test.py的程式碼

python直譯器執行py檔案分為兩個步驟:1.將檔案讀到記憶體,2.解釋執行內容。

二、字元編碼簡介

要搞清楚字元編碼,首先要解決的問題是:什麼是字元編碼?

我們都知道,計算機要想工作必須通電,也就是說‘電’驅使計算機幹活,而‘電’的特性,就是高低電平(高低平即二進位制數1,低電平即二進位制數0),也就是說計算機只認識數位(010101).如果我們想儲存資料,首先得將我們的資料進行一些處理,最終得轉換成010101才能讓計算機識別。

所以必須經過一個過程:

字元--------(翻譯過程)------->數位 

這個過程實際就是一個字元如何對應一個特定數位的標準,這個標準稱之為字元編碼。

那麼問題就來了?作為一種編碼方案,還得解決兩個問題:

  • a.位元組是怎麼分組的,如8 bits或16 bits一組,這也被稱作編碼單元。
  • b.編碼單元和字元之間的對映關係。例如,在ASCII碼中,十進位制65對映到字母A上。

ASCII碼是上個世紀最流行的編碼體系之一,至少在西方是這樣。下圖顯示了ASCII碼中編碼單元是怎麼對映到字元上的。

三、字元編碼的發展史

階段一:現代計算機起源於美國,最早誕生也是基於英文考慮的ASCII

隨著計算機越來越流行,廠商之間的競爭更加激烈,在不同的計算機體系間轉換資料變得十分蛋疼,人們厭煩了這種自定義造成的混亂。最終,計算機制造商一起制定了一個標準的方法來描述字元。他們定義使用一個位元組的低7位來表示字元,並且製作瞭如上圖所示的對照表來對映七個位元的值到一個字元上。例如,字母A是65,c是99,~是126等等, ASCII碼就這樣誕生了。原始的ASCII標準定義了從0到127 的字元,這樣正好能用七個位元表示。

為什麼選擇了7個位元而不是8個來表示一個字元呢?我並不關心。但是一個位元組是8個位元,這意味著1個位元並沒有被使用,也就是從128到255的編碼並沒有被制定ASCII標準的人所規定,這些美國人對世界的其它地方一無所知甚至完全不關心。其它國家的人趁這個機會開始使用128到255範圍內的編碼來表達自己語言中的字元。例如,144在阿拉伯人的ASCII碼中是گ,而在俄羅斯的ASCII碼中是ђ。ASCII碼的問題在於儘管所有人都在0-127號字元的使用上達成了一致,但對於128-255號字元卻有很多很多不同的解釋。你必須告訴計算機使用哪種風格的ASCII碼才能正確顯示128-255號的字元。

總結:ASCII,一個Bytes代表一個字元(英文字元/鍵盤上的所有其他字元),1Bytes=8bit,8bit可以表示0-2**8-1種變化,即可以表示256個字元,ASCII最初只用了後七位,127個數位,已經完全能夠代表鍵盤上所有的字元了(英文字元/鍵盤的所有其他字元),後來為了將拉丁文也編碼進了ASCII表,將最高位也佔用了。

階段二:為了滿足中文,中國人客製化了GBK

GBK:2Bytes代表一個字元;為了滿足其他國家,各個國家紛紛客製化了自己的編碼。日本把日文編到Shift_JIS裡,韓國把韓文編到Euc-kr裡

階段三:萬國碼Unicode編碼

後來,有人開始覺得太多編碼導致世界變得過於複雜了,讓人腦袋疼,於是大家坐在一起拍腦袋想出來一個方法:所有語言的字元都用同一種字元集來表示,這就是Unicode。

Unicode統一用2Bytes代表一個字元,2**16-1=65535,可代表6萬多個字元,因而相容萬國語言.但對於通篇都是英文的文字來說,這種編碼方式無疑是多了一倍的儲存空間(英文字母只需要一個位元組就足夠,用兩個位元組來表示,無疑是浪費空間).於是產生了UTF-8,對英文字元只用1Bytes表示,對中文字元用3Bytes.UTF-8是一個非常驚豔的概念,它漂亮的實現了對ASCII碼的向後相容,以保證Unicode可以被大眾接受。

在UTF-8中,0-127號的字元用1個位元組來表示,使用和US-ASCII相同的編碼。這意味著1980年代寫的檔案用UTF-8開啟一點問題都沒有。只有128號及以上的字元才用2個,3個或者4個位元組來表示。因此,UTF-8被稱作可變長度編碼。

於是位元組流:0100100001000101010011000100110001001111

這個位元組流在ASCII和UTF-8中表示相同的字元:HELLO

至於其他的UTF-16,這裡就不再敘述了。

總結一點:unicode:簡單粗暴,所有字元都是2Bytes,優點是字元----->數位的轉換速度快,缺點是佔用空間大。

utf-8:精準,對不同的字元用不同的長度表示,優點是節省空間,缺點是:字元->數位的轉換速度慢,因為每次都需要計算出字元需要多長的Bytes才能夠準確表示。

因此,記憶體中使用的編碼是unicode,用空間換時間(程式都需要載入到記憶體才能執行,因而記憶體應該是儘可能的保證快);硬碟中或者網路傳輸用utf-8,網路I/O延遲或磁碟I/O延遲要遠大與utf-8的轉換延遲,而且I/O應該是儘可能地節省頻寬,保證資料傳輸的穩定性。

所有程式,最終都要載入到記憶體,程式儲存到硬碟不同的國家用不同的編碼格式,但是到記憶體中我們為了相容萬國(計算機可以執行任何國家的程式原因在於此),統一且固定使用unicode,這就是為何記憶體固定用unicode的原因,你可能會說相容萬國我可以用utf-8啊,可以,完全可以正常工作,之所以不用肯定是unicode比utf-8更高效啊(uicode固定用2個位元組編碼,utf-8則需要計算),但是unicode更浪費空間,沒錯,這就是用空間換時間的一種做法,而存放到硬碟,或者網路傳輸,都需要把unicode轉成utf-8,因為資料的傳輸,追求的是穩定,高效,資料量越小資料傳輸就越靠譜,於是都轉成utf-8格式的,而不是unicode。

四、字元編碼的使用

不管是哪種型別的檔案,只要記住一點:檔案以什麼編碼儲存的,就以什麼編碼方式開啟.

下面我們來看看python中關於編碼出現的問題:

如果不在python檔案指定頭資訊#-*-coding:utf-8-*-,那就使用預設的python2中預設使用ascii,python3中預設使用utf-8

讀取已經載入到記憶體的程式碼(unicode編碼的二進位制),然後執行,執行過程中可能會開闢新的記憶體空間,比如x="hello"

記憶體的編碼使用unicode,不代表記憶體中全都是unicode編碼的二進位制,在程式執行之前,記憶體中確實都是unicode編碼的二進位制,比如從檔案中讀取了一行x="hello",其中的x,等號,引號,地位都一樣,都是普通字元而已,都是以unicode編碼的二進位制形式存放與記憶體中的.但是程式在執行過程中,會申請記憶體(與程式程式碼所存在的記憶體是倆個空間),可以存放任意編碼格式的資料,比如x="hello",會被python直譯器識別為字串,會申請記憶體空間來存放"hello",然後讓x指向該記憶體地址,此時新申請的該記憶體地址儲存也是unicode編碼的hello,如果程式碼換成x="hello".encode('utf-8'),那麼新申請的記憶體空間裡存放的就是utf-8編碼的字串hello了.

瀏覽網頁的時候,伺服器會把動態生成的Unicode內容轉換為UTF-8再傳輸到瀏覽器

如果伺服器端encode的編碼格式是utf-8, 使用者端記憶體中收到的也是utf-8編碼的二進位制

五、Python2與python3編碼區別

1.在python2中有兩種字串型別str和unicode

str型別:

當python直譯器執行到產生字串的程式碼時(例如s='林'),會申請新的記憶體地址,然後將'林'編碼成檔案開頭指定的編碼格式,這已經是encode之後的結果了,所以s只能decode。再次encode就會報錯。

#_*_coding:gbk_*_
2 #!/usr/bin/env python
3 
4 x='林'
5 # print x.encode('gbk') #報錯
6 print x.decode('gbk') #結果:林

在python2中,str就是編碼後的結果bytes,str=bytes,所以在python2中,unicode字元編碼的結果是str/bytes。

#coding:utf-8
s='林' #在執行時,'林'會被以conding:utf-8的形式儲存到新的記憶體空間中

print repr(s) #'xe6x9ex97' 三個Bytes,證明確實是utf-8
print type(s) #<type 'str'>

s.decode('utf-8')
# s.encode('utf-8') #報錯,s為編碼後的結果bytes,所以只能decode

Unicode型別:

當python直譯器執行到產生字串的程式碼時(例如s=u'林'),會申請新的記憶體地址,然後將'林'以unicode的格式存放到新的記憶體空間中,所以s只能encode,不能decode.

s=u'林'
print repr(s) #u'u6797'
print type(s) #<type 'unicode'>


# s.decode('utf-8') #報錯,s為unicode,所以只能encode
s.encode('utf-8') 

特別說明:

當資料要列印到終端時,要注意一些問題.

當程式執行時,比如:x='林';print(x) #這一步是將x指向的那塊新的記憶體空間(非程式碼所在的記憶體空間)中的記憶體,列印到終端,而終端仍然是執行於記憶體中的,所以這列印可以理解為從記憶體列印到記憶體,即記憶體->記憶體,unicode->unicode.對於unicode格式的資料來說,無論怎麼列印,都不會亂碼.python3中的字串與python2中的u'字串',都是unicode,所以無論如何列印都不會亂碼.在windows終端(終端編碼為gbk,檔案編碼為utf-8,亂碼產生)

#分別驗證在pycharm中和cmd中下述的列印結果
s=u'林' #當程式執行時,'林'會被以unicode形式儲存新的記憶體空間中


#s指向的是unicode,因而可以編碼成任意格式,都不會報encode錯誤
s1=s.encode('utf-8')
s2=s.encode('gbk')
print s1 #列印正常否?
print s2 #列印正常否


print repr(s) #u'u6797'
print repr(s1) #'xe6x9ex97' 編碼一個漢字utf-8用3Bytes
print repr(s2) #'xc1xd6' 編碼一個漢字gbk用2Bytes

print type(s) #<type 'unicode'>
print type(s1) #<type 'str'>
print type(s2) #<type 'str'>

2. 在python3中也有兩種字串型別str和bytes

str型別變為unicode型別:

#coding:utf-8
s='林' #當程式執行時,無需加u,'林'也會被以unicode形式儲存新的記憶體空間中,

#s可以直接encode成任意編碼格式
s.encode('utf-8')
s.encode('gbk')

print(type(s)) #<class 'str'>

bytes型別:

#coding:utf-8
s='林' #當程式執行時,無需加u,'林'也會被以unicode形式儲存新的記憶體空間中,

#s可以直接encode成任意編碼格式
s1=s.encode('utf-8')
s2=s.encode('gbk')

print(s) #林
print(s1) #b'xe6x9ex97' 在python3中,是什麼就列印什麼
print(s2) #b'xc1xd6' 同上

print(type(s)) #<class 'str'>
print(type(s1)) #<class 'bytes'>
print(type(s2)) #<class 'bytes'>
  

到此這篇關於python編碼問題彙總的文章就介紹到這了,更多相關python編碼內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com