1. 程式人生 > >徹底弄懂python編碼

徹底弄懂python編碼

def 不能 全世界 寬度 解決方法 增加 str cnblogs style

在編寫python程序的過程中,中英文混用經常會出現編碼問題。圍繞此問題,本文首先介紹編碼的含義及常用編碼,隨後列舉幾個python經常遇到的編碼異常及解決方法,接著列舉筆者在實踐中遇到的異常出現的情景及原因,最後針對編碼問題提出最佳實踐。

一 常見編碼

1.1 unicode編碼

在文本文件中,看到的所有字符,包括中文,都需要在計算機中存儲,而計算機只能存儲0和1這樣的二進制位,所以需要一種方法,將字符映射成數字,然後將數字轉化為二進制位存儲在計算機中。針對字符和數字的映射的問題,產生了unicode編碼,unicode將世界上的所有字符映射為唯一的數字。unicode數字並不是直接就可以轉化為二進制存儲,比如假設中文字符‘中’映射為數字1(00000001),‘國’映射為數字2(00000010),由於漢字很多,單字節並不能表示完所有的漢字,故可能會有漢字的unicode數字為258(00000001 00000010),假設為‘京’,現在在字符串中碰到存儲為00000001 00000010的二進制串,不能區分出其實際代表的是“中國”還是“京”。

針對unicode數字和二進制的映射問題,有兩種解決方法:一種是每個unicode數字用固定寬度的二進制位表示,比如都用兩字節,由此產生了ASCII、GB2312、GBK編碼;另一種是存儲的二進制位除了表示數字之外,還表示每個unicode數字的長度,由此產生了utf-8編碼。

1.2 ASCII編碼

ASCII編碼用單字節表示字符,最高位固定為0,故最多只能表示128個字符,當編程只涉及到英文字符或數字時,不涉及中文字符時,可以使用ASCII編碼。

1.3 GB2312編碼、GBK

GB(GuoBiao)為國標,GBK(GuoBiao Kuozhan)表示國標擴展。GB2312兼容ASCII編碼,對於ASCII可以表示的字符,如英文字符‘A’、‘B’等,在GB2312中的編碼和ASCII編碼一致,占一個字節,對於ASCII不能表示的字符,GB2312用兩個字節表示,且最高位不為0,以防和ASCII字符沖突。例如:‘A’在GB2312中存儲的字節十六進制為41,在ASCII中也是65,中文字符‘中’在GB2312中存儲的兩個字節十六進制為D6D0,最高位為1不為0。

GB2312只有6763個漢字,而漢字特別多。GBK屬於GB2312的擴展,增加了很多漢字,同時兼容GB2312,同樣用兩個字節表示非ASCII字符。

1.4 UTF-8編碼

和GB系列不同,UTF-8可以將全世界所有的unicode數字表示出來。UTF-8兼容ASCII編碼,不兼容GB系列編碼,因此,若文本中UTF-8和GB系列編碼混用,會出現亂碼問題。UTF-8對於每個字符的存儲,用最高二進制位開始連續1的個數表示字的長度,最高位為0表示單字節,用來兼容ASCII字符,為110表示雙字節,非字符首字節的字節都以10開始,如下表格所示。例如:字符‘中’的unicode編碼為2D4E(00101101 01001110),用UTF-8存儲的二進制為E4B8AD(11100100 10111000 10101101 ),存儲在計算機中的首字節為1110開頭,表示此字符占三個字節,去掉開始字節表示長度的1110和其余字節開頭的10,可以得到01001110 00101101(4E2D),可以看到和unicode數字剛好相反,是因為是大端存儲方式,高字節存儲在內存中的低地址端,反過來即為unicode編碼。

字節數二進制編碼格式
單字節 0XXXXXXX
雙字節 110XXXXX 10XXXXXX
三字節 1110XXXX 10XXXXXX 10XXXXXX
四字節 11110XXX 10XXXXXX 10XXXXXX 10XXXXXX
五字節 111110XX 10XXXXXX 10XXXXXX 10XXXXXX 10XXXXXX
六字節 1111110X 10XXXXXX 10XXXXXX 10XXXXXX 10XXXXXX 10XXXXXX

二 python字符序列及編碼問題

上一節對幾種常見的編碼原理做出了介紹,以便理解python由於編碼引起的異常,本節將對python中的字符串作出介紹,並在此基礎上提出幾種常見的編碼異常,並提供解決方案。

2.1 python2和python3字符序列

python2中字符序列有兩種類型:unicode和str。unicode字符序列存儲的元素為unicode字符。如圖2.1所示,unicode_string代表unicode字符序列“中國”,其長度為2,恰好表示兩個unicode字符。

技術分享圖片

圖2.1 unicode字符序列

python2中的另一種字符序列是str類型,str類型的字符序列其實是unicode字符序列encode之後的值,用不同的編碼類型encode,得出的值不一樣。str字符序列的元素為字節,如圖2.2所示,“中國” 的str字符序列長度為6,為UTF-8編碼後所占字節長度。

技術分享圖片

圖2.2 str字符序列

與unicode字符串轉化為str類型用encode相反,str類型的字符序列轉化為unicode字符串,可以通過decode方法,如圖2.3所示:

技術分享圖片

圖2.3 str轉化為unicode

python3中的字符序列也有兩種類型:bytes和str。python3中的bytes和python2中的str相似,str和python2中的unicode相似。這裏要註意,str類型在python3和python2中都有,但含義完全變了。

技術分享圖片

圖2.4 python3的str和bytes字符序列

2.2常見編碼問題

2.2.1 UnicodeEncoderError

將文本轉化為字節序列時,若有字符在目標編碼中沒有定義,則會出現UnicodeEncoderError。如圖2.5所示,由於中文字符在ascii編碼中無定義,則會報出編碼錯誤。對於此類問題,需選擇合適的編碼類型,比如含有中文字符,一般用UTF-8編碼類型對unicode字符串編碼。

技術分享圖片

圖2.5 UnicodeEncodeError示例

2.2.2 UnicodeDecodeError

把二進制序列轉化為文本時,遇到無法轉換的字節序列,則會發生此異常。比如用UTF-8編碼後的二進制序列,用GB2312解碼,由於兩種編碼不兼容,用GB2312不能識別字節序列,則會出現異常,如圖2.6所示。

技術分享圖片

圖2.6 UnicodeDecodeError示例

碰到這種異常,是由於decode使用的編碼和字節序列的編碼不一致,可以用字符編碼偵測包chardet檢測字節序列的編碼,然後再用此編碼解碼。如圖2.7所示:

技術分享圖片

圖2.7 編碼檢測

三 實踐中常見編碼異常場景

3.1 字符串連接

python代碼

1 # -*- coding: utf-8 -*-
2 unicode_string=u中國
3 str_string=中國
4 merge_string= str_string+unicode_string #UnicodeDecodeError: ‘ascii‘ codec can‘t decode byte 0xe4 in position 0: ordinal not in range(128)

python代碼

1 # -*- coding: utf-8 -*-
2 unicode_string=u中國
3 str_string=中國
4 "中國:%s" % str_string
5 #兩種字符序列混用,相當於"中國:%s".decode(‘ascii‘)%unicode_string
6 "中國:%s" % unicode_string #UnicodeDecodeError: ‘ascii‘ codec can‘t decode byte 0xe4 in position 0: ordinal not in range(128)
7 u"中國:%s"%unicode_string
8 #兩種字符序列混用,相當於u"中國:%s"%str_string.decode(‘ascii‘)
9 u"中國:%s"%str_string #UnicodeDecodeError: ‘ascii‘ codec can‘t decode byte 0xe4 in position 0: ordinal not in range(128)

當str類型字符串和unicode類型字符串混合運算時,python默認會將str類型字符串轉化為unicode字符串,由於不知道str類型字符串的編碼格式,會使用 sys.getdefaultencoding() ,而默認的defaultencoding一般是ascii,故會出錯。

3.2 print中文問題

如圖3.1,python打印變量時,操作系統會對變量進行相應的處理,若變量是str類型,則操作系統直接發送到終端顯示,若變量是unicode類型,則操作系統會對變量用sys.stdout.encoding編碼對變量encode,若變量中含有sys.stdout.encoding未定義字符,則會出現UnicodeEncodeError。編碼後字節序列被發送給終端,假若終端設置的編碼和str編碼不一致,終端就會顯示出亂碼。

技術分享圖片

圖3.1 print過程

四 最佳實踐

編寫python程序時,為避免不同類型字符串混用出現編解碼異常,要把編碼和解碼操作放在程序的最外圍來做,程序的核心邏輯統一使用unicode字符類型。下面分別對python2和python3編寫了外圍編碼轉換工具類。

 1 #python2,unicode和utf-8類型的str互相轉換
 2 #file:python2_endecode_helper.py
 3 
 4 # -*- coding: utf-8 -*-
 5 def to_unicode(unicode_or_str):
 6     if isinstance(unicode_or_str, str):
 7         value = unicode_or_str.decode(UTF-8)
 8     else:
 9         value = unicode_or_str
10     return value
11 
12 def to_str(unicode_or_str):
13     if isinstance(unicode_or_str, unicode):
14         value = unicode_or_str.encode(UTF-8)
15     else:
16         value = unicode_or_str
17     return value
18 
19 if __name__==__main__:
20     unicode_string = u中國
21     value = to_str(unicode_string)
22     print type(value) #<type ‘str‘>
23     value = to_unicode(value)
24     print type(value) #<type ‘unicode‘>

#python3,str和bytes類型相互轉換工具類
#file:python3_endecode_helper.py
def to_str(bytes_or_str):
    if isinstance(bytes_or_str,bytes):
        value = bytes_or_str.decode(UTF-8)
    else:
        value = bytes_or_str
    return value

def to_bytes(bytes_or_str):
    if isinstance(bytes_or_str,str):
        value = bytes_or_str.encode(UTF-8)
    else:
        value = bytes_or_str
    return value

if __name__==__main__:
    str_string = u中國
    value = to_bytes(str_string)
    print(type(value)) #<class ‘bytes‘>
    value = to_str(value)
    print(type(value)) #<class ‘str‘>

參考文獻

[1] Brett Slatkin. Effective Python[M]. 北京: 機械工業出版社, 2016: 5-7
[2] Luciano Ramalho. Fluent Python[M]. 北京: 人民郵電出版社, 2017: 89- 91
[3] Jinhaolin. python2編碼總結. https://www.cnblogs.com/jinhaolin/p/5128973.html
[4] In355hz. 也談 Python 的中文編碼處理. http://in355hz.iteye.com/blog/1860787
[5] 董公子. python中文編碼問題:print打印中文異常及顯示亂碼問題分析與解決. https://blog.csdn.net/qq_26580757/article/details/79922043

徹底弄懂python編碼