給妹子講python-S01E08理清python字元編碼的使用方法

醬油哥在掘金發表於2019-02-25

歡迎關注公眾號:python資料科學家

【要點搶先看】

1.python中編、解碼的本質是文字字串和位元組字串的相互轉換
2.python中編、解碼方法舉例及過程解析
3.unicode、latin-1、ASCII編碼方式的相容性問題
4.讀取二進位制檔案

上一集講清楚字元編碼的基礎概念後,我相信這一集再來介紹python中的字元編碼就會容易的多。

通過上一集我們知道,ASCII碼(包括其最常見的超集Latin-1)依賴這樣的一個假設,即每一個字元與一個位元組相匹配,由於存在太多的字元,因此不可避免的會出現問題,Unicode字符集通過使用4個位元組來表示1個字元,則解決了該問題。

首先來介紹一下Python中的兩種字串:

Python中有兩種字串:文字字串和位元組字串。其中文字字串型別被命名為str,內部採用Unicode字符集(相容ASCII碼),而位元組字串則直接用來表示原始的位元組序列(用print函式來列印位元組字串時,若位元組在ascii碼範圍內,則顯示為ascii碼對應的字元,其餘的則直接顯示為16進位制數),該型別被命名為bytes。

看一個簡單的例子:

s = 'apple'
b = b'apple'
print(b)
print(type(b))
print(s)
print(type(s))

b'apple'
<class 'bytes'>
apple
<class 'str'>
複製程式碼

再近距離的看看bytes型別位元組字串,本質上它就是一串單位元組16進位制數

b = b'apple'
print(b[0])
print(b[1:])
print(list(b))

97
b'pple'
[97112112108101]
複製程式碼

【妹子說】那這和編碼、解碼有何關聯呢?

從本質上來說,編碼和解碼就是str和bytes這兩種字串型別之間的互相轉換。

str包含一個encode方法,使用特定編碼將該字串其轉換為一個bytes,這稱之為編碼。bytes類包含了一個decode方法,也接受一個編碼作為單個必要引數,並返回一個str,這稱之為解碼。這種轉換操作是顯式的操作,且必須根據資料被編碼時採用的編碼型別進行解碼。

首先說說編碼,即將unicode的str文字字串轉換為bytes的位元組字串,可以顯式的傳入指定編碼(一般來說採用utf-8編碼),或使用平臺的預設編碼。

s = 'π排球の'
b1 = s.encode('utf-8')
b2 = s.encode()
print(b1)
print(b2)

b'\xcf\x80\xe6\x8e\x92\xe7\x90\x83\xe3\x81\xae'
b'\xcf\x80\xe6\x8e\x92\xe7\x90\x83\xe3\x81\xae'
複製程式碼

那麼我們看看,在不寫編碼的時候,平臺預設的編碼方式到底是什麼

import sys

print(sys.platform)
print(sys.getdefaultencoding())

win32
utf-8
複製程式碼

可以看出我這個平臺預設選擇的是utf-8編碼方式。

接下來我們來比較一下unicode、latin-1、ASCII編碼方式的相容性問題:

首先,非ASCII字元無法使用ASCII編碼轉換成位元組字串

s = 'π排球の'
b = s.encode('ascii')

Traceback (most recent call last):
 File "E:/12homework/12homework.py", line 2in <module>
   b = s.encode('ascii')
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-3:
 ordinal not in range(128)
複製程式碼

其次,Latin-1和unicode編碼方式不相容。

例如,重音字元會在latin-1字符集和unicode字符集中同時存在,但是通過latin-1和unicode編碼方式編出來的位元組流是不一樣的,注意,雖然unicode字符集是包含了latin-1字符集,但是不代表utf-8編碼方式相容latin-1編碼方式。因為unicode字符集中除了ascii字符集外,都是採用多位元組的編碼方式,而latin-1一律採用單位元組的方式

s = 'Äè'
print(s.encode('utf-8'))
print(s.encode('latin-1'))

b'\xc3\x84\xc3\xa8'
b'\xc4\xe8'
複製程式碼

只有ascii字符集中的字元,三種編碼方式得到的結果才完全一致。對unicode進行編碼的時候,針對常規的7位ASCII文字,由於utf-8以及latin-1編碼方式都是相容ASCII的,所以結果都是一樣的。

s = 'abc'
print(s.encode('utf-8'))
print(s.encode('latin-1'))
print(s.encode('ascii'))

b'abc'
b'abc'
b'abc'
複製程式碼

【妹子說】那對應的,再來談談decode解碼方法吧。

將bytes型別字串轉換成str型別的unicode文字字串也是一樣,要麼指定編碼引數,要麼使用平臺的預設引數。這個例子中,我們要操作的位元組字串b是通過utf-8編碼方式對文字字串'π排球の'編碼而形成的。

b = b'\xe6\x8e\x92\xe7\x90\x83'
s1 = b.decode(encoding='utf-8')
s2 = b.decode()
s3 = b.decode(encoding='latin-1')

print(s1)
print(s2)
print(s3)

排球
排球
排çƒ
複製程式碼

值得注意的是,最後一行程式碼想通過latin-1解碼位元組字串,由於位元組字串是通過utf-8編碼形成,因此這樣解碼形成得到的只能是亂碼。

Utf-8編碼是用兩個位元組來表示非ASCII的高128字元,而latin-1則是用一個位元組來一一對應

【妹子說】計算機用二進位制來儲存資訊,而卻能在各種應用中顯示我們需要的文字,這應該是字元編、解碼的應用吧。

很對,下面我們來說說文字檔案讀取時的編、解碼問題

當一個檔案以文字模式開啟的時候,被讀取的二進位制儲存資料(也就是儲存的位元組字串)會自動被解碼(依據顯式提供的編碼名稱或平臺預設的編碼名稱),並且將其返回為一個str。寫入檔案時,會接受一個str,並且將其傳輸到檔案之前自動編碼成位元組字串以供磁碟儲存。

當一個檔案以二進位制模式開啟時,需要在open方法的模式字串引數裡新增一個b,此時讀取的資料不會以任何方式解碼,而是直接返回其原始內容,即一個bytes物件;寫入檔案時,接受一個bytes物件,並且將其傳送到檔案中且不進行修改。

在讀取文字檔案的時候,如果open函式沒有宣告他們如何編碼,python3會因其所執行的系統而選取預設的編碼方式,預設情況下,python3 期望檔案使用 utf-8進行編碼。但由於檔案並不總是在同一個系統中被儲存和開啟,因此會帶來亂碼的風險,所以我們需要顯式的指定編碼。

補充的說明一下,可以很簡單的進行一個分類:處理影象檔案、裝置資料流等,可以使用bytes和二進位制模式檔案處理;而如果要處理的內容實質是文字的內容,例如程式輸出、HTML、國際化文字或CSV或XML檔案,則可能要使用str和文字模式檔案

例如,我們先把AÄBèC用UTF-8編碼後存入utf-8data檔案,再來讀取他,具體看看這裡是如何實現的。

s = 'AÄBèC'

with open('utf-8data','w',encoding='utf-8'as f:
   f.write(s)

with open('utf-8data','r',encoding='utf-8'as f:
   u_str = f.read()
print(u_str)


AÄBèC
複製程式碼

這裡用到的檔案讀寫的方法後面的章節會詳細介紹,現在知道他是什麼就好了。

以二進位制的形式讀取檔案。

還有一種我們之前介紹過的用法,文字字串在儲存到磁碟的時候會編碼成位元組字元,因此我們也可以先以位元組字串的形式從檔案中將其讀取,然後再進行解碼。

這樣做的原因有二,一種是所接收的可能是非文字資料,如一個影象檔案;另一個潛在原因是無法確定所讀取文字檔案的編碼,可能需要依據其他資訊再確定:

with open('utf-8data''rb'as f:
   byte_str = f.read()

print(byte_str)
print(byte_str.decode(encoding='utf-8'))

b'A\xc3\x84B\xc3\xa8C'
AÄBèC
複製程式碼

字串編、解碼在python中很重要,特別是在網路爬蟲等網路應用程式中,在後面的實際應用中會感受到他的作用會越來越明顯。

【妹子說】這一集的內容很多,細緻剖析了python中的兩種字串型別和編、解碼的處理方法。再結合之前的三集,就能從基本使用、字元編、解碼的維度閉環出一個完整的知識網路了,收穫很大。

公眾號二維碼:python資料科學家:

給妹子講python-S01E08理清python字元編碼的使用方法

相關文章