關於python中的字符編碼

一 瞭解字符編碼的知識儲備

  1. 文本編輯器存取文件的原理(nodepad++,pycharm,word)node

    打開編輯器就打開了啓動了一個進程,是在內存中的,因此在編輯器編寫的內容也都是存放與內存中的,斷電後數據丟失python

             於是須要保存到硬盤上,點擊保存按鈕,就從內存中把數據刷到了硬盤上。編程

             在這一點上,咱們編寫一個py文件(沒有執行),跟編寫其餘文件沒有任何區別,都只是在編寫一堆字符而已。windows

      2. python解釋器執行py文件的原理 ,例如python test.py瀏覽器

    第一階段:python解釋器啓動,此時就至關於啓動了一個文本編輯器服務器

    第二階段:python解釋器至關於文本編輯器,去打開test.py文件,從硬盤上將test.py的文件內容讀入到內存中網絡

    第三階段:python解釋器解釋執行剛剛加載到內存中test.py的代碼  編輯器

  總結:this

  1. python解釋器是解釋執行文件內容的,於是python解釋器具有讀py文件的功能,這一點與文本編輯器同樣
  2. 與文本編輯器不同的地方在於,python解釋器不只能夠讀文件內容,還能夠執行文件內容

二 什麼是字符編碼

  計算機要想工做必須通電,也就是說‘電’驅使計算機幹活,而‘電’的特性,就是高低電平(高低平即二進制數1,低電平即二進制數0),也就是說計算機只認識數字 編碼

  編程的目的是讓計算機幹活,而編程的結果說白了只是一堆字符,也就是說咱們編程最終要實現的是:一堆字符驅動計算機幹活

  因此必須通過一個過程:

  字符--------(翻譯過程)------->數字 

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

三 字符編碼的發展史

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

  ASCII:一個Bytes表明一個字符(英文字符/鍵盤上的全部其餘字符),1Bytes=8bit,8bit能夠表示0-2**8-1種變化,便可以表示256個字符

    ASCII最初只用了後七位,127個數字,已經徹底可以表明鍵盤上全部的字符了(英文字符/鍵盤的全部其餘字符)

    後來爲了將拉丁文也編碼進了ASCII表,將最高位也佔用了

階段二:爲了知足中文,中國人定製了GBK

  GBK:2Bytes表明一個字符

  爲了知足其餘國家,各個國家紛紛定製了本身的編碼

  日本把日文編到Shift_JIS裏,韓國把韓文編到Euc-kr

階段三:各國有各國的標準,就會不可避免地出現衝突,結果就是,在多語言混合的文本中,顯示出來會有亂碼。

因而產生了unicode, 統一用2Bytes表明一個字符, 2**16-1=65535,可表明6萬多個字符,於是兼容萬國語言

但對於通篇都是英文的文原本說,這種編碼方式無疑是多了一倍的存儲空間(二進制最終都是以電或者磁的方式存儲到存儲介質中的)

因而產生了UTF-8,對英文字符只用1Bytes表示,對中文字符用3Bytes

須要強調的一點是:

unicode:簡單粗暴,全部字符都是2Bytes,優勢是字符->數字的轉換速度快,缺點是佔用空間大

utf-8:精準,對不一樣的字符用不一樣的長度表示,優勢是節省空間,缺點是:字符->數字的轉換速度慢,由於每次都須要計算出字符須要多長的Bytes纔可以準確表示

  1. 內存中使用的編碼是unicode,用空間換時間(程序都須要加載到內存才能運行,於是內存應該是儘量的保證快)
  2. 硬盤中或者網絡傳輸用utf-8,網絡I/O延遲或磁盤I/O延遲要遠大與utf-8的轉換延遲,並且I/O應該是儘量地節省帶寬,保證數據傳輸的穩定性。

 

四.字符編碼分類

計算機由美國人發明,最先的字符編碼爲ASCII,只規定了英文字母數字和一些特殊字符與數字的對應關係。

ascii用1個字節(8位二進制)表明一個字符

unicode經常使用2個字節(16位二進制)表明一個字符,生僻字須要用4個字節

若是咱們的文檔通篇都是英文,你用unicode會比ascii耗費多一倍的空間,在存儲和傳輸上十分的低效

本着節約的精神,又出現了把Unicode編碼轉化爲「可變長編碼」的UTF-8編碼。UTF-8編碼把一個Unicode字符根據不一樣的數字大小編碼成1-6個字節,經常使用的英文字母被編碼成1個字節,漢字一般是3個字節,只有很生僻的字符纔會被編碼成4-6個字節。若是你要傳輸的文本包含大量英文字符,用UTF-8編碼就能節省空間:

字符 ASCII Unicode UTF-8
A 01000001 00000000 01000001 01000001
x 01001110 00101101 11100100 10111000 10101101

從上面的表格還能夠發現,UTF-8編碼有一個額外的好處,就是ASCII編碼實際上能夠被當作是UTF-8編碼的一部分,因此,大量只支持ASCII編碼的歷史遺留軟件能夠在UTF-8編碼下繼續工做。

五 字符編碼的使用

5.1 文本編輯器一鍋端

 

5.1.2 文本編輯器nodpad++

總結:

不管是何種編輯器,要防止文件出現亂碼(請必定注意,存放一段代碼的文件也僅僅只是一個普通文件而已,此處指的是文件沒有執行前,咱們打開文件時出現的亂碼)

核心法則就是,文件以什麼編碼保存的,就以什麼編碼方式打開

而文件編碼保存時候使用的編碼方式是右下角的編碼方式,而解碼的時候是使用文檔開頭申明的編碼方式,兩種編碼不一樣的時候很容易出現亂碼的狀況。

 

5.2 程序的執行

python test.py   (我再強調一遍,執行test.py的第一步,必定是先將文件內容讀入到內存中)

 

階段一:啓動python解釋器

階段二:python解釋器此時就是一個文本編輯器,負責打開文件test.py,即從硬盤中讀取test.py的內容到內存中

此時,python解釋器會讀取test.py的第一行內容,#coding:utf-8,來決定以什麼編碼格式來讀入內存,這一行就是來設定python解釋器這個軟件的編碼使用的編碼格式這個編碼,

能夠用sys.getdefaultencoding()查看,若是不在python文件指定頭信息#-*-coding:utf-8-*-,那就使用默認的

python2中默認使用ascii,python3中默認使用utf-8

 

 

 

階段三:讀取已經加載到內存的代碼(unicode編碼的二進制),而後執行,執行過程當中可能會開闢新的內存空間,好比x="egon"

內存的編碼使用unicode,不表明內存中全都是unicode編碼的二進制,

在程序執行以前,內存中確實都是unicode編碼的二進制,好比從文件中讀取了一行x="egon",其中的x,等號,引號,地位都同樣,都是普通字符而已,都是以unicode編碼的二進制形式存放與內存中的

可是程序在執行過程當中,會申請內存(與程序代碼所存在的內存是倆個空間),能夠存聽任意編碼格式的數據,好比x="egon",會被python解釋器識別爲字符串,會申請內存空間來存放"hello",而後讓x指向該內存地址,此時新申請的該內存地址保存也是unicode編碼的egon,若是代碼換成x="egon".encode('utf-8'),那麼新申請的內存空間裏存放的就是utf-8編碼的字符串egon了

針對python3以下圖

 

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

 

若是服務端encode的編碼格式是utf-8, 客戶端內存中收到的也是utf-8編碼的二進制。

 

5.3 python2與python3的區別

 

5.3.1 在python2中有兩種字符串類型str和unicode

str類型

當python解釋器執行到產生字符串的代碼時(例如s='林'),會申請新的內存地址,而後將'林'encode成文件開頭指定的編碼格式,這已是encode以後的結果了,因此s只能decode

1 #_*_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) #'\xe6\x9e\x97' 三個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') 
複製代碼

打印到終端

對於print須要特別說明的是:

當程序執行時,好比

x='林'

print(x) #這一步是將x指向的那塊新的內存空間(非代碼所在的內存空間)中的內存,打印到終端,而終端仍然是運行於內存中的,因此這打印能夠理解爲從內存打印到內存,即內存->內存,unicode->unicode

 

對於unicode格式的數據來講,不管怎麼打印,都不會亂碼

python3中的字符串與python2中的u'字符串',都是unicode,因此不管如何打印都不會亂碼

在pycharm中

在windows終端

 

 

可是在python2中存在另一種非unicode的字符串,此時,print x,會按照終端的編碼執行x.decode('終端編碼'),變成unicode後,再打印,此時終端編碼若與文件開頭指定的編碼不一致,亂碼就產生了

在pycharm中(終端編碼爲utf-8,文件編碼爲utf-8,不會亂碼)

 

在windows終端(終端編碼爲gbk,文件編碼爲utf-8,亂碼產生)

 

 

 

思考題:

分別驗證在pycharm中和cmd中下述的打印結果

複製代碼
#coding:utf-8
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) #'\xe6\x9e\x97' 編碼一個漢字utf-8用3Bytes
print repr(s2) #'\xc1\xd6' 編碼一個漢字gbk用2Bytes

print type(s) #<type 'unicode'>
print type(s1) #<type 'str'>
print type(s2) #<type 'str'>
複製代碼

 

5.3.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是bytes

複製代碼
#coding:utf-8
s='林' #當程序執行時,無需加u,'林'也會被以unicode形式保存新的內存空間中,

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



print(s) #林
print(s1) #b'\xe6\x9e\x97' 在python3中,是什麼就打印什麼
print(s2) #b'\xc1\xd6' 同上

print(type(s)) #<class 'str'>
print(type(s1)) #<class 'bytes'>
print(type(s2)) #<class 'bytes'>
複製代碼