Python學習之路day3-字符編碼與轉碼

1、基礎概念

  • 字符與字節

    字符是相對於人類而言的可識別的符號標識,是一種人類語言,如中文、英文、拉丁文甚至甲骨文、梵語等等。
    字節是計算機內部識別可用的符號標識(0和1組成的二進制串,機器語言),屬於機器語言。
    人與計算機交互就須要在人類語言和機器語言之間來回轉換,所以當把各類各樣的字符存儲或輸入到計算機時,最終都必須以字節形式來表示;反之當計
算機輸出相應信息給人類用戶時,最終也須要以人類可識別的字符形式來傳遞。
    綜上所述,字符與人類更爲接近,而字節則與計算機(機器)更爲接近。
   
上面的概念區分看起來很是清楚了,但實際應用尤爲是在進行python開發又很容易犯迷糊,各類編碼什麼的一旦有些許模糊就會出現意想不到的結果。那麼怎麼區分字符與字節呢?

    通用原則:
    Unicode纔是真正的字符串,而ASCII、UTF-八、GBK等編碼格式表示的都是字節碼。
html

     緣由是:字符串是由字符構成,字符在計算機硬件中經過二進制形式存儲,這種二進制形式就是字節碼(編碼)。若是直接使用 「字符串↔️字符↔️二進制表示(編碼)」,會增長不一樣類型編碼之間轉換的複雜性。因此引入了一個抽象層,「字符串↔️字符↔️與存儲無關的表示↔️二進制表示(編碼)」 ,這樣,能夠用一種與存儲無關的形式表示字符,不一樣的編碼之間轉換時能夠先轉換到這個抽象層,而後再轉換爲其餘編碼形式。在這裏,unicode 就是 「與存儲無關的表示」,是一種字符集,而ASCII、UTF-八、GBK等就是字符串對應的「二進制表示」。(以上文字絕大部分引自
連接:https://www.zhihu.com/question/23374078/answer/28710945,做者:flyer,原文博客成死連接了 https://link.zhihu.com/?target=http%3A//flyer103.diandian.com/post/2014-03-09/40061199665
    所以,Unicode解決了人類識別計算機語言的兼容性和可用性問題,是一種字符串(只是一種字符集,而沒有編碼格式),而ASCII、UTF-八、GBK等是解決字符信息(Unicode)如何在計算機內部存儲和表示的,是二進制形式的字節碼。
   
關於這點,咱們能夠在Python的官方文檔中常常能夠看到這樣的描述"Unicode string" , " translating a Unicode string into a sequence of bytes",咱們寫代碼是寫在文件中的,而字符是以字節形式保存在文件中的,所以當咱們在文件中定義個字符串時被當作字節串也是能夠理解的。可是,咱們須要的是字符串,而不是字節串。(這段文字引用自http://www.cnblogs.com/yyds/p/6171340.html)Python2.0沒有對此做嚴格區分,好在Python 3裏已經開始嚴格區分了,而且默認的字符集就是Unicode。換句話講,咱們經過pycharm進行python 3.0開發時,編輯器裏呈現的代碼都是字符串,都是Unicode,不管咱們在文件頭聲明使用什麼文件格式(文件頭的申明決定了源代碼文件以什麼樣的二進制格式存儲)。node

  • 編碼

    前面章節所述從人類可識別的字符到計算機可識別處理(存儲傳輸)的字節的轉換,就是一個編碼的過程,即encode。而ASCII、UTF-八、GBK等,則決定了如何把字符轉換爲二進制字節碼(轉換規則)和轉換後的二進制碼的內容,因此它們是具體的編碼格式(同一個符號能夠有多種二進制字節碼的表示)。借用網上某大神搬出來的通訊理論強化一下,unicode是信源編碼,對字符集數字化;utf8是信道編碼,爲更好的存儲和傳輸。python

  • 解碼

    解碼就是編碼的逆過程,把計算機中存儲運行的二進制字節碼轉換爲字符的過程,即decode。
簡單總結:
     編碼(encode):將Unicode字符串轉換特定字符編碼對應的字節串的過程和規則
     解碼(decode):將特定字符編碼的字節串轉換爲對應的Unicode字符串的過程和規則

可見,不管是編碼仍是解碼,都須要一個重要因素,就是特定的字符編碼。由於一個字符用不一樣的字符編碼進行編碼後的字節值以及字節個數大部分狀況下是不一樣的,反之亦然。

    仍是來一張圖示吧:
linux

  image

  • Python源代碼的執行過程

    首先明確一下基本概念,咱們藉助Pycharm來進行Python程序開發時,須要與編輯器和解釋器打交道,Pycharm做爲IDE就是編輯器,提升咱們編寫代碼的效率;而咱們安裝的來自官方的Python程序(在Pycharm中運行時顯示的python.exe)則是解釋器。
    結合上面章節對字符串和字節碼相互轉換的過程講解,能夠肯定在開發過程當中會在如下兩個地方用到字符編碼:
windows

  1. 磁盤文件的存儲寫入和讀取
    這是對源代碼文件編寫後的保存和讀取處理過程,由編輯器Pycharm自動完成。保存時須要encode,讀取時則須要decode,這是由編輯器指定的工程或文件的字符編碼決定的(Pycharm中仍是推薦用默認的UTF-8)。
  2. 程序執行時的輸入和輸出
    這是程序執行時的信息的表示過程,由解釋器Python.exe完成。同理輸入時須要encode(轉換成機器碼),輸出時須要decode。請注意,這裏的程序執行時的輸出不只包括程序運行後的各類print,更重要的是包括程序啓動運行時Python解釋器將源代碼文件中的字節碼讀取後轉換爲Unicode(decode)的過程,在此基礎上Python解釋器纔會進行後續的處理(如print一個字符串或者變量)。這個過程須要咱們指定源代碼文件中保存字節時所使用的字符編碼。
    總結以下:
    經過文件頭指定字符編碼:
      1 # -*- coding:utf-8 -*-

        Python源代碼執行過程:
        image
         圖片和上述思想主要摘自http://www.cnblogs.com/yyds/p/6171340.html
         補充一下,Python解釋器經過指定的字符編碼把字節碼轉換爲Unicode只是程序執行的剛剛開始,後續解釋器還須要把Unicode字符串翻譯爲c代碼(默認的cpython),而後轉換成二進制流經過操做系統的相應接口調用CPU來執行二進制數據(機器只識別二進制數據),最後以Unicode方式返回咱們須要的結果。
編輯器

2、Unicode的由來與UTF-8的關係

     理清了字符串和字節碼各自的概念和轉換過程, 如今不得不引入Unicode的由來以及它與UTF-8的關係了。
     Unicode的由來
    
衆所周知計算機由美國人發明, 因爲英文字母相對固定簡單, 美國大叔僅僅經過不到128位的ASCII就解決了字符編碼問題. 隨着計算機技術的不斷髮展和推廣,其餘國家也陸續開始使用計算機.遺憾的是對於非英語母語國家而言,因爲母語相對於英語字母的複雜性和多樣性,不到128位的ASCII無法扛起字符編碼的重擔.因而不少多家都結合本身的母語開發了適用於本國語言的字符編碼體系,咱們如今熟知的GBK,GB2312等國標都是那個時期的產物。
     因爲字符編碼的各自爲政,使得不一樣國家之間的交流變得很不暢,一不當心就踩到亂碼了. 因而ISO集全球之力量,通過充分調研和科學設計,制定了能夠一應俱全、能搞定世界上基本全部語言編碼問題的Unicode,人稱萬國碼。
     請注意,Unicode自己並非一種字符編碼,而是一種字符集。若是直接使用 「字符串↔️字符↔️二進制表示(編碼)」,會增長不一樣類型編碼之間轉換的複雜性。因此引入了一個抽象層,「字符串↔️字符↔️與存儲無關的表示↔️二進制表示(編碼)」 ,這樣,能夠用一種與存儲無關的形式表示字符,不一樣的編碼之間轉換時能夠先轉換到這個抽象層,而後再轉換爲其餘編碼形式。在這裏,unicode 就是 「與存儲無關的表示」,是一種字符集,而ASCII、UTF-八、GBK等就是字符串對應的「二進制表示」。
函數

     Unicode與UTF-8的關係
    
不少地方會很簡單地講UTF-8就是Unicode的壓縮和優化版,通過前面章節的分析咱們發現這種說法不嚴謹。仍是先複習下通訊理論,unicode是信源編碼,對字符集數字化;utf8是信道編碼,爲更好的存儲和傳輸。Unicode是一張字符與數字的映射,可是這裏的數字被稱爲代碼點(code point), 實際上就是十六進制的數字。Python官方文檔中對Unicode字符串、字節串與編碼之間的關係有這樣一段描述:
     Unicode字符串是一個代碼點(code point)序列,代碼點取值範圍爲0到0x10FFFF(對應的十進制爲1114111)。這個代碼點序列在存儲(包括內存和物理磁盤)中須要被表示爲一組字節(0到255之間的值),而將Unicode字符串轉換爲字節序列的規則稱爲編碼

     簡單來說,Unicode字符集能夠將字符用必定範圍的數字來進行數字化,至於怎麼把數字化表示爲二進制,一千我的眼裏有一千個哈姆雷特,這個過程就交給字符編碼來顯神通了,UTF-8恰好是其中一種且通用性很強的一種。所以能夠講utf8是對unicode字符集進行編碼的一種編碼方式,是對unicode字符集的二進制體現;Unicode是解決如何向人類展示可被識別的字符問題,而UTF-8是解決如何將人類可識別的字符轉換爲可被機器識別的字節碼問題。
post

3、Python中的字符編碼

  • 默認字符編碼

    設想一個問題,若是咱們在編寫Python代碼文件時沒有指定編碼格式,程序在執行時解釋器又怎麼把字節碼轉換成Unicode呢?這裏由Python中默認的字符編碼來決定。當咱們的默認字符編碼與編輯器裏設置的字符編碼不一致時,可經過文件頭來進行字符編碼格式的聲明來解決,而若是此時沒有文件頭裏面的聲明,則會直接致使解碼失敗。Python2中解釋器默認的字符編碼是ASCII,Python3中解釋器默認的字符編碼是UTF-8。這就是Python2裏默認狀況下不支持中文字符的緣由(沒有經過文件頭申明文件編碼就調用默認的ASCII進行解碼,顯然ASCII碼裏沒有中文對應的字符)。測試

    Python解釋器的默認編碼可經過如下方式確認:優化

[root@node1 ~]# python
Python 2.6.6 (r266:84292, Aug 18 2016, 15:13:37)
[GCC 4.4.7 20120313 (Red Hat 4.4.7-17)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.getdefaultencoding()
'ascii'
>>>

C:\Users\Beyondi>python
Python 3.5.2 (v3.5.2:4def2a2901a5, Jun 25 2016, 22:18:55) [MSC v.1900 64 bit (AM
D64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.getdefaultencoding()
'utf-8'
>>>

Python2下未聲明字符編碼狀況下對中文字符的支持狀況:

  1 [root@node1 python]# cat test.py
  2 #! /usr/bin/env python
  3 s='你好,世界!'
  4 print(s)
  5 [root@node1 python]# python test.py
  6   File "test.py", line 2
  7 SyntaxError: Non-ASCII character '\xe4' in file test.py on line 2, but no encoding declared; see http://www.python.org/peps/pep-0263.html for details

上面的報錯很明顯了,第2行中須要打印的字符串ASCII不支持,而且也沒有對字符編碼進行顯示聲明。
如今在文件頭增長字符編碼的顯示聲明:

  1 [root@node1 python]# cat test.py
  2 # -*- coding:utf-8 -*-
  3 #! /usr/bin/env python
  4 s='你好,世界!'
  5 print(s)
  6 [root@node1 python]# python test.py
  7 你好,世界!

增長正確的字符編碼聲明後,程序在執行時解釋器會經過指定的字符編碼UTF-8來對保存的字節碼進行解碼,而後進行後續的print處理,因爲Linux環境下默認的字符編碼也是UTF-8,因此中文字符就能正常顯示了。若是把這段程序放到windows下運行(Python3解釋器),狀況又是怎樣的呢?
經過記事本複製代碼後保存爲test.py,而後在cmd下運行:

  1 D:\python\S13\Day3>type test.py
  2 # -*- coding:utf-8 -*-
  3 #! /usr/bin/env python
  4 s='你好,世界!'
  5 print(s)
  6 D:\python\S13\Day3>python test.py
  7   File "test.py", line 3
  8 SyntaxError: (unicode error) 'utf-8' codec can't decode byte 0xc4 in position 0:
  9  invalid continuation byte

從輸出結果來看程序仍是報錯了,何故?咱們不是在文件頭中聲明瞭字符編碼了嗎?這裏的問題在於咱們經過記事本保存代碼文件時默認的字符編碼是windows中文版下的GBK,而程序執行時Python解釋器會用咱們在文件頭中聲明的UTF-8去解碼成Unicode來進行後續的處理,用UTF-8去解碼GBK編碼,固然不能解了。因此要注意的是,須要保持解釋器的字符編碼與編輯器的字符編碼一致。因此強烈建議在Pycharm中把默認的字符編碼設置爲UTF-8,省得給本身挖坑。
實際測試代表Pycharm是一個比較智能的IDE,若是經過頁面右下角的按鈕改變了文件編碼,運行程序時文件頭部的編碼聲明會自動調整以保持二者一致,此時你會發現右下角的改變文件編碼按鈕會有以下提示:
image

此時咱們須要把聲明的字符編碼改爲與編輯器一致的字符編碼GBK便可(或者修改編輯器保存文件時的字符編碼也OK,總之要保持一致):

  1 D:\python\S13\Day3>type test.py
  2 # -*- coding:GBK -*-
  3 #! /usr/bin/env python
  4 s='你好,世界!'
  5 print(s)
  6 D:\python\S13\Day3>
  7 D:\python\S13\Day3>
  8 D:\python\S13\Day3>python test.py
  9 你好,世界!
 10 
 11 D:\python\S13\Day3>

補充一點,這裏若是把須要打印輸出的字符串內容改成英文字符,那麼不修改文件頭部的字符編碼也能夠正常輸出:

  1 D:\python\S13\Day3>type test.py
  2 __author__ = 'Maxwell'
  3 #!/usr/bin/env python
  4 # -*- coding: utf-8 -*-
  5 s = 'Hello, world!'
  6 print(s)
  7 D:\python\S13\Day3>
  8 D:\python\S13\Day3>
  9 D:\python\S13\Day3>python test.py
 10 Hello, world!

這是由於英文字符在UTF-8和GBK下的二進制碼是相同的。

  1 __author__ = 'Maxwell'
  2 #!/usr/bin/env python
  3 # -*- coding: utf-8 -*-
  4 s = 'Hello, world!'
  5 print(s.encode('utf-8'))
  6 print(s.encode('GBK'))
  7 print(type(s.encode('utf-8')))
  8 print(type(s.encode('GBK')))
  9 
 10 
 11 輸出:
 12 b'Hello, world!'
 13 b'Hello, world!'
 14 <class 'bytes'>  #注意encode後類型變爲二進制了
 15 <class 'bytes'>

因此,除非特別須要,強烈建議在代碼一向保持用英文字符。

        綜上所述,強烈建議遵循如下原則進行Python開發:

    1. 將IDE的字符編碼設置爲UTF-8
    2. 習慣性地在代碼文件頭部分明確聲明使用UTF-8字符編碼 -*- coding:utf-8 –*-
      這樣能夠保持程序對Python2解釋器的良好兼容性。
  • Python 2 VS Python 3
    從事Python開發必定要了解Python2與Python3的差別性,在此從字符編碼相關的角度略微展開。
    首先,Python2的默認編碼是ASCII,而Python3的默認編碼是UTF-8。
    其次,Python2和Python3在字符串與字節碼的處理上大相徑庭
    Python2中字符串有str和Unicode兩種類型,但str表示的是通過各類字符編碼編碼後的二進制字節碼,Unicode則是沒有編碼的標準文本。Unicode通過編碼轉換爲爲str,str通過解碼後還原成Unicode。這裏把字符串和字節碼混在一塊兒了。
    Python3中str類型即爲Unicode字符串,單獨設立的bytes表示通過編碼處理後的二進制字節碼。所以Python3中對字符串和字節碼做了嚴格的區分。str通過編碼後轉換爲bytes類型的字節碼,反過來bytes類型的字節碼通過解碼則能夠還原爲Unicode字符串。
    仍是來一張形象的圖示吧:
    Horizontal Cross Functional Template (1)

    經過上圖可看出,Python3中的str相似於Python2中的Unicode,而Python3中的bytes則相似於Python2中的str,但請注意僅僅是相似而不是等同於,由於還存在其餘層面的區別:
    (1)Python2中可直接查看str的Unicode字節序列
      1 [root@node1 python]# python
      2 Python 2.6.6 (r266:84292, Aug 18 2016, 15:13:37)
      3 [GCC 4.4.7 20120313 (Red Hat 4.4.7-17)] on linux2
      4 Type "help", "copyright", "credits" or "license" for more information.
      5 >>> s=u'你好,世界!'
      6 >>> s
      7 u'\u4f60\u597d\uff0c\u4e16\u754c!'

    (2)Python3中open函數加上了encoding參數,默認傳遞的字符編碼是UTF-8,對文件進行read或write時,只接受包含Unicode的字符串。對於二進制文件,則須要以rb,wb或ab模式打開。
    借用大神的一張形象展現一下(引用自 http://www.cnblogs.com/yuanchenqi/articles/5956943.html):
    image

4、實戰字符編碼轉碼

    通過前面章節的理論鋪墊,是時候來展開字符編碼與轉碼的重點了。
    先從Python3開始:
    前面章節闡述過,Unicode是一個抽象層,與存儲無關的表示,同時又一應俱全,能夠很方便地與其餘字符編碼進行轉換,所以它能夠做爲中間代理人來進行不一樣編碼之間的轉換,以簡化不一樣編碼之間直接轉換的複雜度。實際應用中正是這樣:

 
    經過例子來實戰一下:

  1 __author__ = 'Maxwell'
  2 #!/usr/bin/env python
  3 # -*- coding: utf-8 -*-
  4 s = '你好,世界!'
  5 print(s.encode('utf-8'))
  6 print(s.encode('GBK'))
  7 s_to_gbk = s.encode('GBK')
  8 print(s_to_gbk.decode('GBK').encode('utf-8'))
  9 print(s_to_gbk.decode('GBK').encode('utf-8').decode('utf-8')) #連續轉碼處理,GBK解碼後以utf-8方式編碼,最後解碼還原成Unicode
 10 
 11 輸出:
 12 b'\xe4\xbd\xa0\xe5\xa5\xbd\xef\xbc\x8c\xe4\xb8\x96\xe7\x95\x8c\xef\xbc\x81'
 13 b'\xc4\xe3\xba\xc3\xa3\xac\xca\xc0\xbd\xe7\xa3\xa1'
 14 b'\xe4\xbd\xa0\xe5\xa5\xbd\xef\xbc\x8c\xe4\xb8\x96\xe7\x95\x8c\xef\xbc\x81'
 15 你好,世界!

  注意:
  1. 以上程序輸出代表,Unicode字符串通過編碼處理後當即變成bytes類型(輸出結果以b開頭,固然也能夠經過type函數來驗證肯定),須要打印時必須解碼轉換爲Unicode字符串;
  2. 文件頭部的顯式字符編碼聲明僅僅決定了Python解釋器在執行程序的開始階段,會經過什麼字符編碼去打開解碼保存的二進制字節碼代碼文件,它不改變程序代碼中定義的str類型是Unicode字符串這一事實(此時定義的字符串s仍然能夠被各類encode)。Python3中內嵌原生地支持Unicode,定義的str類型默認就是Unicode字符串。

  Python2版本下的狀況:

  1 [root@node1 python]# python
  2 Python 2.6.6 (r266:84292, Aug 18 2016, 15:13:37)
  3 [GCC 4.4.7 20120313 (Red Hat 4.4.7-17)] on linux2
  4 Type "help", "copyright", "credits" or "license" for more information.
  5 >>> s='你好,世界!'
  6 >>> s_to_unicode=s.decode('utf-8')
  7 >>> s_to_gbk=s_to_unicode.encode('GBK')
  8 >>> print(type(s))
  9 <type 'str'>
 10 >>> print(type(s_to_unicode))
 11 <type 'unicode'>
 12 >>> s
 13 '\xe4\xbd\xa0\xe5\xa5\xbd\xef\xbc\x8c\xe4\xb8\x96\xe7\x95\x8c!'
 14 >>> s_to_gbk
 15 '\xc4\xe3\xba\xc3\xa3\xac\xca\xc0\xbd\xe7!'
 16 >>> print(s)
 17 你好,世界!
 18 >>> print(s_to_gbk.decode('GBK'))
 19 你好,世界!
 20 >>> print(type(s_to_gbk.decode('GBK'))) # decode以後類型變爲Unicode
 21 <type 'unicode'>

Python2下的字符串是二進制字節碼形式,能夠經過str= u’Hello,world!’直接轉換爲Unicode。打印時能夠對str自己打印,也能夠對其轉換後的Unicode打印,由於Python2下沒有對字節和字符做嚴格區分。

  1 >>> s='你好,世界!'
  2 >>> print(s)
  3 你好,世界!
  4 >>> h=u'你好,世界!'
  5 >>> print(h)
  6 你好,世界!
  7 >>> h
  8 u'\u4f60\u597d\uff0c\u4e16\u754c!'
  9 >>> s
 10 '\xe4\xbd\xa0\xe5\xa5\xbd\xef\xbc\x8c\xe4\xb8\x96\xe7\x95\x8c!'
 11 >>> print(s.decode('utf-8'))
 12 你好,世界!
相關文章
相關標籤/搜索