Python中的字符編碼是個老生常談的話題,同行們都寫過不少這方面的文章。有的人云亦云,也有的寫得很深刻。近日看到某知名培訓機構的教學視頻中再次談及此問題,講解的仍是不盡人意,因此纔想寫這篇文字。一方面,梳理一下相關知識,另外一方面,但願給其餘人些許幫助。html
Python2的 默認編碼 是ASCII,不能識別中文字符,須要顯式指定字符編碼;Python3的 默認編碼 爲Unicode,能夠識別中文字符。python
相信你們在不少文章中都看到過相似上面這樣「對Python中中文處理」的解釋,也相信你們在最初看到這樣的解釋的時候確實以爲明白了。但是時間久了以後,再重複遇到相關問題就會以爲貌似理解的又不是那麼清楚了。若是咱們瞭解上面說的默認編碼的做用是什麼,咱們就會更清晰的明白那句話的含義。程序員
須要說明的是,「字符編碼是什麼」,以及「字符編碼的發展過程」 不是本節討論的話題,這些內容能夠參考我以前的 < <這篇文章> > 。編程
一個字符不等價於一個字節,字符是人類可以識別的符號,而這些符號要保存到計算的存儲中就須要用計算機可以識別的字節來表示。一個字符每每有多種表示方法,不一樣的表示方法會使用不一樣的字節數。這裏所說的不一樣的表示方法就是指字符編碼,好比字母A-Z均可以用ASCII碼錶示(佔用一個字節),也能夠用UNICODE表示(佔兩個字節),還能夠用UTF-8表示(佔用一個字節)。字符編碼的做用就是將人類可識別的字符轉換爲機器可識別的字節碼,以及反向過程。編程語言
UNICDOE纔是真正的字符串,而用ASCII、UTF-八、GBK等字符編碼表示的是字節串。關於這點,咱們能夠在Python的官方文檔中常常能夠看到這樣的描述"Unicode string" , " translating a Unicode string into a sequence of bytes"編輯器
咱們寫代碼是寫在文件中的,而字符是以字節形式保存在文件中的,所以當咱們在文件中定義個字符串時被當作字節串也是能夠理解的。可是,咱們須要的是字符串,而不是字節串。一個優秀的編程語言,應該嚴格區分二者的關係並提供巧妙的完美的支持。JAVA語言就很好,以致於瞭解Python和PHP以前我歷來沒有考慮過這些不該該由程序員來處理的問題。遺憾的是,不少編程語言試圖混淆「字符串」和「字節串」,他們把字節串當作字符串來使用,PHP和Python2都屬於這種編程語言。最能說明這個問題的操做就是取一個包含中文字符的字符串的長度:google
注意:Windows的cmd終端字符編碼默認爲GBK,所以在cmd輸入的中文字符須要用兩個字節表示編碼
>>> # Python2 >>> a = 'Hello,中國' # 字節串,長度爲字節個數 = len('Hello,')+len('中國') = 6+2*2 = 10 >>> b = u'Hello,中國' # 字符串,長度爲字符個數 = len('Hello,')+len('中國') = 6+2 = 8 >>> c = unicode(a, 'gbk') # 其實b的定義方式是c定義方式的簡寫,都是將一個GBK編碼的字節串解碼(decode)爲一個Uniocde字符串 >>> >>> print(type(a), len(a)) (<type 'str'>, 10) >>> print(type(b), len(b)) (<type 'unicode'>, 8) >>> print(type(c), len(c)) (<type 'unicode'>, 8) >>>
Python3中對字符串的支持作了很大的改動,具體內容會在下面介紹。翻譯
先作下科普:UNICODE字符編碼,也是一張字符與數字的映射,可是這裏的數字被稱爲代碼點(code point), 實際上就是十六進制的數字。code
Python官方文檔中對Unicode字符串、字節串與編碼之間的關係有這樣一段描述:
Unicode字符串是一個代碼點(code point)序列,代碼點取值範圍爲0到0x10FFFF(對應的十進制爲1114111)。這個代碼點序列在存儲(包括內存和物理磁盤)中須要被表示爲一組字節(0到255之間的值),而將Unicode字符串轉換爲字節序列的規則稱爲編碼。
這裏說的編碼不是指字符編碼,而是指編碼的過程以及這個過程當中所使用到的Unicode字符的代碼點與字節的映射規則。這個映射沒必要是簡單的一對一映射,所以編碼過程也沒必要處理每一個可能的Unicode字符,例如:
將Unicode字符串轉換爲ASCII編碼的規則很簡單--對於每一個代碼點:
將Unicode字符串轉換爲UTF-8編碼使用如下規則:
簡單總結:
可見,不管是編碼仍是解碼,都須要一個重要因素,就是特定的字符編碼。由於一個字符用不一樣的字符編碼進行編碼後的字節值以及字節個數大部分狀況下是不一樣的,反之亦然。
咱們都知道,磁盤上的文件都是以二進制格式存放的,其中文本文件都是以某種特定編碼的字節形式存放的。對於程序源代碼文件的字符編碼是由編輯器指定的,好比咱們使用Pycharm來編寫Python程序時會指定工程編碼和文件編碼爲UTF-8,那麼Python代碼被保存到磁盤時就會被轉換爲UTF-8編碼對應的字節(encode過程)後寫入磁盤。當執行Python代碼文件中的代碼時,Python解釋器在讀取Python代碼文件中的字節串以後,須要將其轉換爲UNICODE字符串(decode過程)以後才執行後續操做。
上面已經解釋過,這個轉換過程(decode,解碼)須要咱們指定文件中保存的字節使用的字符編碼是什麼,才能知道這些字節在UNICODE這張萬國碼和統一碼中找到其對應的代碼點是什麼。這裏指定字符編碼的方式你們都很熟悉,以下所示:
# -*- coding:utf-8 -*-
那麼,若是咱們沒有在代碼文件開始的部分指定字符編碼,Python解釋器就會使用哪一種字符編碼把從代碼文件中讀取到的字節轉換爲UNICODE代碼點呢?就像咱們配置某些軟件時,有不少默認選項同樣,須要在Python解釋器內部設置默認的字符編碼來解決這個問題,這就是文章開頭所說的「默認編碼」。所以你們所說的Python中文字符問題就能夠總結爲一句話:當沒法經過默認的字符編碼對字節進行轉換時,就會出現解碼錯誤(UnicodeEncodeError)。
Python2和Python3的解釋器使用的默認編碼是不同的,咱們能夠經過sys.getdefaultencoding()來獲取默認編碼:
>>> # Python2 >>> import sys >>> sys.getdefaultencoding() 'ascii' >>> # Python3 >>> import sys >>> sys.getdefaultencoding() 'utf-8'
所以,對於Python2來說,Python解釋器在讀取到中文字符的字節碼嘗試解碼操做時,會先查看當前代碼文件頭部是否有指明當前代碼文件中保存的字節碼對應的字符編碼是什麼。若是沒有指定則使用默認字符編碼"ASCII"進行解碼致使解碼失敗,致使以下錯誤:
SyntaxError: Non-ASCII character '\xc4' in file xxx.py on line 11, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details
對於Python3來說,執行過程是同樣的,只是Python3的解釋器以"UTF-8"做爲默認編碼,可是這並不表示能夠徹底兼容中文問題。好比咱們在Windows上進行開發時,Python工程及代碼文件都使用的是默認的GBK編碼,也就是說Python代碼文件是被轉換成GBK格式的字節碼保存到磁盤中的。Python3的解釋器執行該代碼文件時,試圖用UTF-8進行解碼操做時,一樣會解碼失敗,致使以下錯誤:
SyntaxError: Non-UTF-8 code starting with '\xc4' in file xxx.py on line 11, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details
-*- coding:utf-8 -*-
其實Python3中對字符串支持的改進,不只僅是更改了默認編碼,而是從新進行了字符串的實現,並且它已經實現了對UNICODE的內置支持,從這方面來說Python已經和JAVA同樣優秀。下面咱們來看下Python2與Python3中對字符串的支持有什麼區別:
Python2中對字符串的支持由如下三個類提供
class basestring(object) class str(basestring) class unicode(basestring)
執行help(str)和help(bytes)會發現結果都是str類的定義,這也說明Python2中str就是字節串,然後來的unicode對象對應纔是真正的字符串。
#!/usr/bin/env python # -*- coding:utf-8 -*- a = '你好' b = u'你好' print(type(a), len(a)) print(type(b), len(b))
輸出結果:
(<type 'str'>, 6) (<type 'unicode'>, 2)
Python3中對字符串的支持進行了實現類層次的上簡化,去掉了unicode類,添加了一個bytes類。從表面上來看,能夠認爲Python3中的str和unicode合二爲一了。
class bytes(object) class str(object)
實際上,Python3中已經意識到以前的錯誤,開始明確的區分字符串與字節。所以Python3中的str已是真正的字符串,而字節是用單獨的bytes類來表示。也就是說,Python3默認定義的就是字符串,實現了對UNICODE的內置支持,減輕了程序員對字符串處理的負擔。
#!/usr/bin/env python # -*- coding:utf-8 -*- a = '你好' b = u'你好' c = '你好'.encode('gbk') print(type(a), len(a)) print(type(b), len(b)) print(type(c), len(c))
輸出結果:
<class 'str'> 2 <class 'str'> 2 <class 'bytes'> 4
上面提到,UNICODE字符串能夠與任意字符編碼的字節進行相互轉換,如圖:
那麼你們很容易想到一個問題,就是不一樣的字符編碼的字節能夠經過Unicode相互轉換嗎?答案是確定的。
字節串-->decode('原來的字符編碼')-->Unicode字符串-->encode('新的字符編碼')-->字節串
#!/usr/bin/env python # -*- coding:utf-8 -*- utf_8_a = '我愛中國' gbk_a = utf_8_a.decode('utf-8').encode('gbk') print(gbk_a.decode('gbk'))
輸出結果:
我愛中國
字符串-->encode('新的字符編碼')-->字節串
#!/usr/bin/env python # -*- coding:utf-8 -*- utf_8_a = '我愛中國' gbk_a = utf_8_a.encode('gbk') print(gbk_a.decode('gbk'))
輸出結果:
我愛中國
最後須要說明的是,Unicode不是有道詞典,也不是google翻譯器,它並不能把一箇中文翻譯成一個英文。正確的字符編碼的轉換過程只是把同一個字符的字節表現形式改變了,而字符自己的符號是不該該發生變化的,所以並非全部的字符編碼之間的轉換都是有意義的。怎麼理解這句話呢?好比GBK編碼的「中國」轉成UTF-8字符編碼後,僅僅是由4個字節變成了6個字節來表示,但其字符表現形式還應該是「中國」,而不該該變成「你好」或者「China」。
前面花了很大的篇幅介紹概念和理論,後面注重實踐,但願對他人有所幫助。