python編碼與解碼


基本概念

字符:表示數據和信息的字母、數字或其他符號。在電子計算機中,每一個字符與一個二進制編碼相對應。

字符的標識(碼位):是0-1114111的數字,在Unicode標准中以4-6個十六進制數字表示,而且加前綴“U+”。例如,字母A的碼位是U+0041,歐元符號的碼位是U+20AC.

字符的具體表述取決於所用的編碼。編碼是在碼位和字節序列之間轉換時使用的算法。在UTF-8編碼中,A(U+0041)的碼位編碼成單個字節\x41,而在UTF-16LE編碼中編碼成兩個字節\x41\x00。歐元符號(U+20AC)在UFT-8編碼中是三個字節\xe2\x82\xac,而在UTF-16LE編碼中編碼成兩個字節\xac\x20。

編碼:把碼位轉換成字節序列(通俗來說:把字符串轉換成用於存儲或傳輸的字節序列,python中是.encode())

解碼:把字節序列轉換成碼位(通俗來說:把字節序列轉換成人類可讀的文本字符串,python中是.decode())

>>> s = 'café'
>>> len(s) # Unicode字符數量
4
>>> b = s.encode('utf8') # 編碼為bytes
>>> b
b'caf\xc3\xa9' 
>>> len(b) # 字節數
5
>>> b.decode('utf8') # 解碼
'café

字節概要

新的二進制序列類型在很多方面與 Python 2 的 str 類型不同。首先要知道,Python 內置了兩種基本的二進制序列類型:Python 3 引入的不可變bytes 類型和 Python 2.6 添加的可變 bytearray 類型。(Python 2.6 也引入了 bytes 類型,但那只不過是 str 類型的別名,與 Python 3 的bytes 類型不同。)

  bytes 或 bytearray 對象的各個元素是介於 0~255(含)之間的整數,而不像 Python 2 的 str 對象那樣是單個的字符。然而,二進制序列的切片始終是同一類型的二進制序列,包括長度為 1 的切片,如示例:

>>> cafe = bytes('café', encoding='utf_8')
>>> cafe
b'caf\xc3\xa9'
>>> cafe[0]
>>> cafe[:1]
b'c'
>>> cafe_arr = bytearray(cafe)
>>> cafe_arr
bytearray(b'caf\xc3\xa9')
>>> cafe_arr[-1:]
bytearray(b'\xa9')

二進制序列有個類方法是 str 沒有的,名為 fromhex,它的作用是解析十六進制數字對(數字對之間的空格是可選的),構建二進制序列:

>>> bytes.fromhex('31 4B CE A9')
b'1K\xce\xa9'

python基本編碼解碼器

python自帶了超過100種編碼解碼器,用於在文本和字節中轉換。每個編碼解碼器有一個名稱,如'utf-8',通常有幾個別名如'utf-8'別名有'utf8'、''utf_8、'U8'。

 

ISO-8859-1(latin1):該編碼是單字節編碼,向下兼容ASCII,其編碼范圍是0x00-0xFF,0x00-0x7F之間完全和ASCII一致,0x80-0x9F之間是控制字符,0xA0-0xFF之間是文字符號。

cp1252ISO-8859-1的超集,添加了一些有用的符號。

cp437:IBM PC最初的字符集,包含框圖符號,於ISO-8859-1不兼容。

gb2312:簡體中文陳舊標准,亞洲語言使用較廣泛的多字節編碼。

utf-8:目前Web中最常見的8位編碼;與ASCII兼容

utf-16le:UTF-16的16位編碼方案的一種形式;所有UTF-16支持轉義序列表示超過U+FFFF的碼位。

編碼存在的問題

 

處理UnicodeError

多數非 UTF 編解碼器只能處理 Unicode 字符的一小部分子集。把文本轉換成字節序列時,如果目標編碼中沒有定義某個字符,那就會拋出UnicodeEncodeError 異常,除非把 errors 參數傳給編碼方法或函數,對錯誤進行特殊處理。

city = 'São Paulo'

u8 = city.encode('utf_8')
print('utf-8:', u8)
#結果:  utf-8: b'S\xc3\xa3o Paulo'      'utf_?' 編碼能處理任何字符串

u16 = city.encode('utf_16') print('utf-16:', u16) #結果:  utf-16: b'\xff\xfeS\x00\xe3\x00o\x00 \x00P\x00a\x00u\x00l\x00o\x00'  
iso = city.encode('iso8859_1') print('iso:', iso) #結果:  iso: b'S\xe3o Paulo'  'iso8859_1' 編碼也能處理字符串 'São Paulo
cpr37 = city.encode('cp437')   #結果:  報錯      'cp437' 無法編碼 'ã'(帶波形符的“a”)
cp_ig = city.encode('cp437', errors='ignore') print('cp ignore:', cp_ig) #結果:  cp ignore: b'So Paulo'
cp_rp = city.encode('cp437', errors='replace') print('cp replace:', cp_rp) #結果:  cp replace: b'S?o Paulo'

如上處理策略:

1.error='ignore' 處理方式悄無聲息地跳過無法編碼的字符;這樣做通常很是不妥

2.編碼時指定error='replace',把無法編碼的字符替換成'?';數據損壞了,但是用戶知道出現了問題

3.把errors參數注冊額外字符串,方法是把一個名稱和一個錯誤處理函數傳遞給codes.register_error函數。(參考python標准庫)

 

處理UnicodeDecodeError

不是每個字節都包含有效的ASCALL字符,也不是每個字符序列都是有效的UTF-8或UTF16。因此,把二進制序列轉換為文本時,如果假設是這兩個編碼中的一個,遇到無法轉換的字節序列時會拋出UnicodeDecodeError。

octets = b'Montr\xe9al'     #iso8859_1編碼

cp1252 = octets.decode('cp1252')   
print(cp1252)
#結果:Montréal    正確解碼   cp1252是iso8859_1的超集

iso = octets.decode('iso8859_7')
print(iso)
#結果:Montrιal   錯誤解碼

koi8_r = octets.decode('koi8_r')
print(koi8_r)
#結果:MontrИal   錯誤解碼

#utf_8 = octets.decode('utf-8')
print(utf_8)
#結果:異常    不是有效的utf-8字符串

new_utf_8 = octets.decode('utf-8', errors='replace')
print(new_utf_8)
#結果:Montr�al    官方指定的錯誤處理方式

 

處理SyntaxError

python3默認使用utf-8編碼源碼,如果加載的模塊中包含utf-8之外的數據,而且沒有聲明編碼,則會拋出SyntaxError異常。linux和OS X系統大都使用utf-8,因此在打開windows系統中使用cp1252編碼的.py文件可能發生這種情況。

為了修正這個問題,可以在文件頂部添加一個神奇的coding注釋,  coding:cp1252

 

判斷文件字節序列編碼

使用命令行工具chardetect(python庫Chardet提供的)

 

 處理文本文件

處理文本的最佳實踐是“Unicode 三明治”(如圖下圖所示)。 意思是,要盡早把輸入(例如讀取文件時)的字節序列解碼成字符串。這種三明治中的“肉片”是程序的業務邏輯,在這里只能處理字符串對象。在其他處理過程中,一定不能編碼或解碼。對輸出來說,則要盡量晚地把字符串編碼成字節序列。多數 Web 框架都是這樣做的,使用框架時很少接觸字節序列。例如,在 Django 中,視圖應該輸出 Unicode 字符串;Django 會負責把響應編碼成字節序列,而且默認使用 UTF-8 編碼。

 

 

#打開一個文件cafe.txt並寫入內容,w是對文件的模式操作(寫操作), encoding是對文件操作的編碼
fp = open('cafe.txt', 'w', encoding='utf_8')
fp_len = fp.write('café')
print('fp的io信息:', fp)
print('寫入到文件中內容的長度:', fp_len)
fp.close()

#獲取文件的內容
fp2 = open('cafe.txt')
print('fp2的io信息:', fp2)
'''
因為和上面的寫入的編碼不同,所以直接以默認的編碼打開,無法處理é而引發異常
'''
#print(fp2.read())
fp2.close()

#解決fp2無法或許文件內容的方法指定打開的時候編碼
fp3 = open('cafe.txt', encoding='utf-8')
print('fp3的io信息:', fp3)
print('fp3中的文件內容:', fp3.read())
fp3.close()

fp4 = open('cafe.txt', 'rb')
print('fp4的io信息:', fp4)
print('fp4的文件內容:', fp4.read().decode('utf-8'))
fp4.close()

#另外一種不太可取的解決方案, errors可以設置成replace或者ignore
fp5 = open('cafe.txt', 'r', errors='ignore')
print('fp5的io信息:', fp5)
print('fp5的文件內容:', fp5.read())

 以上代碼結果:

fp的io信息: <_io.TextIOWrapper name='cafe.txt' mode='w' encoding='utf_8'>
寫入到文件中內容的長度: 4
fp2的io信息: <_io.TextIOWrapper name='cafe.txt' mode='r' encoding='US-ASCII'>
fp3的io信息: <_io.TextIOWrapper name='cafe.txt' mode='r' encoding='utf-8'>
fp3中的文件內容: café
fp4的io信息: <_io.BufferedReader name='cafe.txt'>
fp4的文件內容: café
fp5的io信息: <_io.TextIOWrapper name='cafe.txt' mode='r' encoding='US-ASCII'>
fp5的文件內容: caf

Unicode規范化

使用unicodedata.normalize提供的nuicode規范化

NFC使用最少碼位構成等價的字符串,而NFD把組合字符分解成基字符和單獨的組合字符

>>> s1 = 'café'
>>> s2 = 'cafe\u0301'
>>> s1, s2
('café', 'café')
>>> len(s1), len(s2)
(4, 5)
>>> s1 == s2
False

 實際  é   ===    'e\u0301'  是成立的,而Python 看到的是不同的碼位序列,因此判定二者不相等,則需要使用NFC或NFD。

from unicodedata import normalize


s1 = 'café' # 把"e"和重音符組合在一起
s2 = 'cafe\u0301' # 分解成"e"和重音符
print('s1和s2的長度:', len(s1), len(s2))

print('NFC標准化處理以后的s1,s2的長度:', len(normalize('NFC', s1)), len(normalize('NFC', s2)))
print('NFD標准化處理以后的s1,s2的長度:', len(normalize('NFD', s1)), len(normalize('NFD', s2)))
print(normalize('NFC', s1), normalize('NFC', s2))


#結果:
s1和s2的長度: 4 5
NFC標准化處理以后的s1,s2的長度: 4 4
NFD標准化處理以后的s1,s2的長度: 5 5
café café

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM