Python進階-編解碼問題全解

image.png

默認討論的都是Python3.6。html

1. 計算機編碼的發展歷程

1.1 編碼的誕生

計算機的存儲與計算都是以二進制的形式進行的;所以對於邏輯符號Char(數字,字母,中文,數學字符,其餘字符如製表符)須要有對應的二進制碼錶示,這就是編碼的做用。java

編碼有兩個方面,一個是字符集,一個是字符集對應的編碼規則/算法。通常來講,人們認爲Unicode編碼是一種字符集,utf-8與utf-16是具體的字符編碼規則/算法。python

可是若是要把unicode看作編碼規則也是能夠的,有n個字符就用十進制n的二進制形式來表示這個字符集便可,最先的ascii碼就是這樣作的。可是這樣很難實現unicode做爲萬國碼須要知足的一些特性:1.向下兼容,2.易於拓展,3.兼顧存儲與傳輸性能。linux

1.2 ASCII

ASCII(American Standard Code for Information Interchange,美國標準信息交換代碼)是基於拉丁字母的一套電腦編碼系統,主要用於顯示現代英語和其餘西歐語言。它是現今最通用的單字節編碼系統,並等同於國際標準ISO/IEC 646。web

標準 ASCII 碼使用 7 個二進位對字符進行編碼。基本的 ASCII 字符集共有 128 個字符,其中有 96 個可打印字符,包括經常使用的字母、數字、標點符號等,另外還有 32 個控制字符。0~31表示控制字符如回車、退格、刪除等;32~126表示打印字符便可以經過鍵盤輸入而且能顯示出來的字符。
image.png!
在ASCII中,字符集(charset)編碼與編碼encoding方案等價。而UNICODE中字符集(charset)編碼與編碼encoding方案不等價。算法

1.2.1 ISO-8859-1(latin1)

ASCII只能表示128個字符,顯示是不能徹底表示完的,因此ISO-8859-1(latin-1)擴展了ASCII編碼,在ASCII編碼之上又增長了西歐語言、希臘語、泰語、阿拉伯語、希伯來語對應的文字符號,它是向下兼容ASCII編碼的。sql

1.3 ANSI

ASCII是由美國國家標準學會(American National Standard Institute , ANSI )制定的,是一種標準的單字節字符編碼方案,用於基於文本的數據;ascii是ansi標準,包含128個字符(7 bits)。數據庫

可是,咱們常說的ANSI編碼,一般特指windows平臺的一種基於ANSI標準的ASCII擴展碼,他將ASCII碼擴展到8bits,增長了0x80-0xff共128個字符。ANSI碼僅在前126個與ASCII碼相同。vim

  • 在簡體中文Windows操做系統中,ANSI 編碼表明 GBK 編碼
  • 在英文Windows操做系統中,ANSI 編碼表明 ASCII編碼
  • 在繁體中文Windows操做系統中,ANSI編碼表明Big5
  • 在日文Windows操做系統中,ANSI 編碼表明 Shift_JIS 編碼

1.3.1 GB2312>GBK>GB18030

GBK,全稱爲Chinese Internal Code Specification,即漢字內碼擴展規範,於1995年制定[gb2312爲1980年]。它主要是擴展了GB2312,在它的基礎上又加了更多的漢字,它一共收錄了21003個漢字。GBK是向下兼容GB2312編碼的,也就是說GB2312編碼的漢字能夠用GBK正常解碼不會出現亂碼,但用GBK編碼的漢字用GB2312解碼就不必定了。windows

GB18030全稱漢字內碼擴展規範,是如今最新的內碼字集於2000年發佈,並於2001年強制執行,包含了中國大部分少數民族的語言字符,收錄漢字數超過70000餘個。

它主要採用單字節、雙字節、四字節對字符編碼,它是向下兼容GB2312和GBK的,雖然是我國的強制使用標準,但在實際生產中不多用到,用得最多的反而是GBK和GB2312。

1.4 Unicode

爲了本身的語言能在計算機中正常顯示,每一個國家和地區都有各自的編碼,因此編碼多了誰也不認識對方的編碼,這時候ISO組織就提出了一種新的編碼叫UNICODE編碼讓全球的文化、字符、符號都能支持。UNICODE在制定時計算機容量已不是問題,因此設計成了固定兩個字節,全部的字符都用16位表示,包括以前只佔8位的英文字符等,因此會形成空間的浪費,UNICODE在很長的一段時間內都沒有獲得推廣應用。

UNICODE能夠理解爲一種字符集映射方案;基於該方案也能夠直接進行二進制編碼(例如utf-16實現),可是效率會比較低,所以又產生了utf-8實現。

1.4.1 UTF-16

UTF-16是UNICODE的具體實現,16即16位,UTF-16便是這個來由,定義了UNICODE字符在計算機中的存儲方式,UTF-16一樣使用了兩個字節來表示任何字符,這樣使得操做字符串很是高效,這也是java把UTF-16做爲字符在內存中存儲的格式的重要緣由。

1.4.2 UTF-8

雖然UTF-16很高效,但也是UNICODE最大的壞處,使得全部單字節字符必定要佔兩個字節,存儲空間放大了一倍,這明顯消耗了資源,不符合如今互聯網高速發展的現狀。因此有了UTF-8,它是UNICODE的一種可變長度字符編碼的實現。

  • 它可使用1~6個定長字節來編碼UNICODE字符。
  • UTF-8對ASCII字符使用單字節存儲,單個字符損壞也不會影響後面的字符,因此UTF-8很是適合在網絡上面傳輸,也是如今使用最普遍的編碼之一。
  • 若是要表示中文,UTF-8編碼效率要大於GBK,小於UTF-16,因此它也是除了GBK以外最理想的編碼方式。
  • UTF-16適合在磁盤與內存之間使用,字符和字節的相互轉換會更加簡單和高效,但不適合在網絡上傳輸,由於網絡傳輸可能會損壞字節流。

2. Python3編解碼體系

2.1 bytes和str

Python3嚴格區分了 str 和 bytes 兩種類型。Python3不會以任意隱式的方式混用 str 和 bytes。所以使用者不能拼接字符串和字節包,也沒法在字節包裏搜索字符串(反之亦然),也不能將字符串傳入參數爲字節包的函數(反之亦然)。

>>> client.send("test str") 
Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: a bytes-like object is required, not 'str' 
>>> client.send(b"test str") #將參數轉換成 bytes 類型 8 #返回發送的數據長度

以文本爲例,Python在工做中會將文本數據讀取到內存decode爲unicode編碼,前面說了unicode編碼的UTF-16實現因爲操做字符串很是高效,所以很適合在內存中使用。在用戶的角度,這些decode爲unicode編碼的字符就是Python中的str。

在內存中完成運算以後,將文本數據寫回到磁盤中,須要將數據再encode爲utf-8(通常狀況下使用utf-8,其餘的也是能夠的)。在用戶的角度,這些encode爲utf-8的字符就是Python中的bytes。

>>> b = bytes('中文', 'utf-8') 
>>> b 
b'\\xe4\\xb8\\xad\\xe6\\x96\\x87' 
>>> b.decode('Windows 1252') 
'ä¸\\xadæ–‡' 
>>> b.decode('ISO8859-7') 
'δΈ\\xadζ\\x96\\x87' 
>>> string = b.decode('utf-8') 
>>> string 
'中文' 
>>> b = string.encode('utf-8') 
>>> b 
b'\\xe4\\xb8\\xad\\xe6\\x96\\x87'

2.2 編解碼工做流程

咱們能夠將Python工做區看做一個水池,一個入口負責從硬盤讀取內容轉爲unicode到內存進行運算與處理,一個出口負責將內存中的數據轉爲utf-8或者其餘編碼形式保存到硬盤中。
image.png

2.3 Python常見編解碼錯誤及解決辦法

2.3.1 Non-ASCII character

通常出如今數據讀取中。緣由爲系統默認編碼類型與文件保存時的編碼類型不一致。

2.3.1.1 查看系統編碼類型:
sys.getdefaultencoding() 
'utf-8'
2.3.1.2 查看文件編碼類型:

vim 打開文件,切換到底線命令模式,在最底一行輸入以下命令:

:set fileencoding 
fileencoding=utf-16le

除此以外,還有基於file指定,enca指定的方式。

2.3.1.3 實現匹配
  • 一個方向是在Python讀取文件的過程當中指定與待讀取文件同樣的編碼方式
  • 另外一個方向是將待讀取文件轉化爲utf-8。

哪一個高效用哪一個便可。

2.3.2 UnicodeEncodeError

通常出如今數據輸出中,例如print過程當中可能出現的:UnicodeEncodeError: 'ascii' codec can't encode character '\\U0001f621' in posit。

2.3.2.1 標準輸出報錯

標準輸出即Python中的print(); print方法調用以後會根據默認的編碼方式將要print的str從unicode轉化爲對應的編碼並交接給操做系統,最後在顯示屏上輸出。

如下指令能夠查詢默認的標準輸出編碼方法:

>>> import sys 
>>> sys.stdout.encoding 
'ANSI\_X3.4-1968'

'ANSI_X3.4-1968' 只能支持標準的ascii碼字符輸出,那麼怎樣可以修改默認的標準輸出編碼標準從而支持更多的輸出字符呢?

1. 修改環境變量

export PYTHONIOENCODING=utf-8

2. 每次運行腳本時帶上

有的時候可能並不想改動環境變量,那麼就直接在腳本前指定。

PYTHONIOENCODING=utf-8 python code.py

3. 代碼中修改

import codecs 
sys.stdout = codecs.getwriter("utf-8")(sys.stdout.detach()) 
sys.stdout.write("Your content....")

4. 操做系統設定

更改系統默認的語言編碼設定,從而默認標註輸出爲utf-8。最簡單的一種處理方式以下,該方式不必定能支持中文,可是範圍確定大於ASCII碼。

LC_ALL=C.UTF-8

2.3.2.2 寫入報錯

有時候往文本文件或者數據庫寫入也會遇到編碼報錯(例如用默認的ASCII碼去編碼中文字符),解決辦法通常爲指定寫入的編碼方式。

# 指定將str編碼爲bytes的標準爲utf-8(也是默認的)
with open('test.txt', 'w', encoding='utf8') as f:  

    f.write('something')

# 'wb'模式只能直接寫入bytes類型
with open('test.txt', 'wb') as f:  

    f.write('something'.encode('utf-8'))

4. 關聯軟硬件編解碼問題

在實際的開發過程當中,只理解Python的編解碼模型是不夠的,還須要有一個更加宏觀的認識,例如Python與操做系統的編解碼關係,Python與其餘軟件組件的編解碼關係。

4.1 數據庫編解碼

數據庫通常都有客戶端和服務端。例如在A機器上進行項目開發,在B機器上部署數據庫,那麼A機器上的sqlclient就是客戶端,B機器上的sqlserver就是服務端;A機器須要經過網絡遠程與B機器的數據庫進行交互完成數據的讀寫。

在A機器上,從B機器的sqlserver讀寫文件都統一用utf-8則不會有什麼問題;可是在B機器上若是要本地讀取數據則可能須要特別設置讀取的編碼類型爲utf-8,否則可能會出現讀出來亂碼的問題。

4.2 操做系統LANG與locale

程序運行使用一套語言須要有字符集(數據)和字體(顯示),Locale是根據計算機用戶所使用的語言,所在國家或者地區,以及當地的文化傳統所定義的一個軟件運行時的語言環境。

在locale環境中,有一組變量,表明國際化環境中的不一樣設置;其中有一個叫作LC_CTYPE ,用於字符分類和字符串處理,控制全部字符的處理方式,包括字符編碼,字符是單字節仍是多字節,如何打印等。是最重要的一個環境變量。若是在開發過程當中遇到了編碼問題,操做系統自己的語言環境也是須要去斟酌考慮的

更多細節參考
https://www.cnblogs.com/rusking/p/3695993.html

4.3 建議

4.3.1 規範編碼

爲了確保後臺流程編解碼的順暢進行,最好將系統環境編碼,文本編輯器,文件編碼和數據庫編碼設置爲同樣的。

4.3.2 保證代碼源文件編碼

在Python2中,py文件的默認編碼是ASCII,在源代碼文件中,若是用到非ASCII字符,須要在文件頭部進行編碼聲明;Python3默認源文件編碼爲utf-8。

5. REFERENCE

1. 架構師必須掌握的各類編碼

http://www.javashuo.com/article/p-mcymxpng-pb.html

2. PYTHON編碼和解碼

https://blog.csdn.net/can0227/article/details/83240705

3. linux查看文件編碼格式

http://www.javashuo.com/article/p-vpewsubs-bk.html

相關文章
相關標籤/搜索