bytes實(shí)例包含的是原始數(shù)據(jù),即8位的無(wú)符號(hào)值(通常按照ASCII編碼標(biāo)準(zhǔn)來(lái)顯示)。
a=b'hello'print(list(a))print(a)>>>[104,101,108,108,111]b'hello'
str實(shí)例包含的是Unicode碼點(diǎn)(codepoint,也叫作代碼點(diǎn)),這些碼點(diǎn)與人類語(yǔ)言之中的文本字符相對(duì)應(yīng)。
a='au0300propos'print(list(a))print(a)>>>['a','`','','p','r','o','p','o','s']àpropos
大家一定要記?。簊tr實(shí)例不一定非要用某一種固定的方案編碼成二進(jìn)制數(shù)據(jù),bytes實(shí)例也不一定非要按照某一種固定的方案解碼成字符串。
要把Unicode數(shù)據(jù)轉(zhuǎn)換成二進(jìn)制數(shù)據(jù),必須調(diào)用str的encode方法。
要把二進(jìn)制數(shù)據(jù)轉(zhuǎn)換成Unicode數(shù)據(jù),必須調(diào)用bytes的decode方法。
調(diào)用這些方法的時(shí)候,可以明確指出自己要使用的編碼方案,也可以采用系統(tǒng)默認(rèn)的方案,通常是指UTF-8(但有時(shí)也不一定,下面就會(huì)講到這個(gè)問(wèn)題)。
編寫(xiě)Python程序的時(shí)候,一定要把解碼和編碼操作放在界面最外層來(lái)做,讓程序的核心部分可以使用Unicode數(shù)據(jù)來(lái)運(yùn)作,這種辦法通常叫作Unicode三明治(Unicodesandwich)。程序的核心部分,應(yīng)該用str類型來(lái)表示Unicode數(shù)據(jù),并且不要鎖定到某種字符編碼上面。
這樣可以讓程序接受許多種文本編碼(例如Latin-1、ShiftJIS及Big5),并把它們都轉(zhuǎn)化成Unicode,也能保證輸出的文本信息都是用同一種標(biāo)準(zhǔn)(最好是UTF-8)編碼的。
兩種不同的字符類型與Python中兩種常見(jiàn)的使用情況相對(duì)應(yīng):
開(kāi)發(fā)者需要操作原始的8位值序列,序列里面的這些8位值合起來(lái)表示一個(gè)應(yīng)該按UTF-8或其他標(biāo)準(zhǔn)編碼的字符串。
開(kāi)發(fā)者需要操作通用的Unicode字符串,而不是操作某種特定編碼的字符串。
我們通常需要編寫(xiě)兩個(gè)輔助函數(shù)(helperfunction),以便在這兩種情況之間轉(zhuǎn)換,確保輸入值類型符合開(kāi)發(fā)者的預(yù)期形式。
第一個(gè)輔助函數(shù)接受bytes或str實(shí)例,并返回str:
defto_str(bytes_or_str):ifisinstance(bytes_or_str,bytes):value=bytes_or_str.decode('utf-8')else:value=bytes_or_strreturnvalue#Instanceofstrprint(repr(to_str(b'foo')))print(repr(to_str('bar')))>>>'foo''bar'
第二個(gè)輔助函數(shù)也接受bytes或str實(shí)例,但它返回的是bytes:
defto_bytes(bytes_or_str):ifisinstance(bytes_or_str,str):value=bytes_or_str.encode('utf-8')else:value=bytes_or_strreturnvalue#Instanceofbytesprint(repr(to_bytes(b'foo')))print(repr(to_bytes('bar')))
在Python中使用原始的8位值與Unicode字符串時(shí),有兩個(gè)問(wèn)題要注意。
第一個(gè)問(wèn)題是,bytes與str這兩種類型似乎是以相同的方式工作的,但其實(shí)例并不相互兼容,所以在傳遞字符序列的時(shí)候必須考慮好其類型。
可以用+操作符將bytes添加到bytes,str也可以這樣。
print(b'one'+b'two')print('one'+'two')>>>b'onetwo'onetwo
但是不能將str實(shí)例添加到bytes實(shí)例:
b'one'+'two'>>>Traceback...TypeError:can'tconcatstrtobytes
也不能將bytes實(shí)例添加到str實(shí)例:
'one'+b'two'>>>Traceback...TypeError:canonlyconcatenatestr(not"bytes")tostr
bytes與bytes之間可以用二元操作符(binaryoperator)來(lái)比較大小,str與str之間也可以:
assertb'red'>b'blue'assert'red'>'blue'
但是str實(shí)例不能與bytes實(shí)例比較:
assert'red'>b'blue'
反過(guò)來(lái)也一樣,也就是說(shuō)bytes實(shí)例不能與str實(shí)例比較:
assertb'blue'<'red'
判斷bytes與str實(shí)例是否相等,總是會(huì)評(píng)估為假(False),即便這兩個(gè)實(shí)例表示的字符完全相同,它們也不相等。例如,在下面這個(gè)例子里,它們表示的字符串都相當(dāng)于ASCII編碼之中的foo。
print(b'foo'=='foo')>>>False
兩種類型的實(shí)例都可以出現(xiàn)在%操作符的右側(cè),用來(lái)替換左側(cè)那個(gè)格式字符串(formatstring)里面的%s。
print(b'red%s'%b'blue')print('red%s'%'blue')>>>b'redblue'redblue
如果格式字符串是bytes類型,那么不能用str實(shí)例來(lái)替換其中的%s,因?yàn)镻ython不知道這個(gè)str應(yīng)該按照什么方案來(lái)編碼。
print(b'red%s'%'blue')
但反過(guò)來(lái)卻可以,也就是說(shuō)如果格式字符串是str類型,則可以用bytes實(shí)例來(lái)替換其中的%s,問(wèn)題是,這可能跟你想要的結(jié)果不一樣。
print('red%s'%b'blue')>>>redb'blue'
這樣做,會(huì)讓系統(tǒng)在bytes實(shí)例上面調(diào)用__repr__方法,然后用這次調(diào)用所得到的結(jié)果替換格式字符串里的%s,因此程序會(huì)直接輸出b'blue',而不是像你想的那樣,輸出blue本身。
第二個(gè)問(wèn)題發(fā)生在操作文件句柄的時(shí)候,這里的句柄指由內(nèi)置的open函數(shù)返回的句柄。這樣的句柄默認(rèn)需要使用Unicode字符串操作,而不能采用原始的bytes。習(xí)慣了Python2的開(kāi)發(fā)者,尤其容易碰到這個(gè)問(wèn)題,進(jìn)而導(dǎo)致程序出現(xiàn)奇怪的錯(cuò)誤。例如,向文件寫(xiě)入二進(jìn)制數(shù)據(jù)的時(shí)候,下面這種寫(xiě)法其實(shí)是錯(cuò)誤的。
withopen('data.bin','w')asf:f.write(b'?????')>>>Traceback...TypeError:write()argumentmustbestr,notbytes
程序發(fā)生異常是因?yàn)樵谡{(diào)用open函數(shù)時(shí),指定的是'w'模式,所以系統(tǒng)要求必須以文本模式寫(xiě)入。如果想用二進(jìn)制模式,那應(yīng)該指定'wb'才對(duì)。在文本模式下,write方法接受的是包含Unicode數(shù)據(jù)的str實(shí)例,不是包含二進(jìn)制數(shù)據(jù)的bytes實(shí)例。所以,我們得把模式改成'wb'來(lái)解決該問(wèn)題。
withopen('data.bin','wb')asf:f.write(b'?????')
讀取文件的時(shí)候也有類似的問(wèn)題。例如,如果要把剛才寫(xiě)入的二進(jìn)制文件讀出來(lái),那么不能用下面這種寫(xiě)法。
withopen('data.bin','r')asf:data=f.read()
程序出錯(cuò),是因?yàn)樵谡{(diào)用open函數(shù)時(shí)指定的是'r'模式,所以系統(tǒng)要求必須以文本模式來(lái)讀取。若要用二進(jìn)制格式讀取,應(yīng)該指定'rb'。以文本模式操縱句柄時(shí),系統(tǒng)會(huì)采用默認(rèn)的文本編碼方案處理二進(jìn)制數(shù)據(jù)。
所以,上面那種寫(xiě)法會(huì)讓系統(tǒng)通過(guò)bytes.decode把這份數(shù)據(jù)解碼成str字符串,再用str.encode把字符串編碼成二進(jìn)制值。然而對(duì)于大多數(shù)系統(tǒng)來(lái)說(shuō),默認(rèn)的文本編碼方案是UTF-8,所以系統(tǒng)很可能會(huì)把b'?????'當(dāng)成UTF-8格式的字符串去解碼,于是就會(huì)出現(xiàn)上面那樣的錯(cuò)誤。為了修正錯(cuò)誤,需要把模式改成'rb'。
withopen('data.bin','rb')asf:data=f.read()assertdata==b'?????'
另一種改法是在調(diào)用open函數(shù)的時(shí)候,通過(guò)encoding參數(shù)明確指定編碼標(biāo)準(zhǔn),以確保平臺(tái)特有的一些行為不會(huì)干擾代碼的運(yùn)行效果。例如,假設(shè)剛才寫(xiě)到文件里的那些二進(jìn)制數(shù)據(jù)表示的是一個(gè)采用'cp1252'標(biāo)準(zhǔn)(cp1252是一種老式的Windows編碼方案)來(lái)編碼的字符串,則可以這樣寫(xiě):
withopen('data.bin','r',encoding='cp1252')asf:data=f.read()assertdata=='?òó??'
這樣程序就不會(huì)出現(xiàn)異常了,但返回的字符串也與讀取原始字節(jié)數(shù)據(jù)所返回的有很大區(qū)別。通過(guò)這個(gè)例子,我們要提醒自己注意當(dāng)前操作系統(tǒng)默認(rèn)的編碼標(biāo)準(zhǔn)(可以執(zhí)行python3-c'importlocale;print(locale.getpreferredencoding())'命令查看),了解它與你所期望的是否一致。如果不確定,那就在調(diào)用open時(shí)明確指定encoding參數(shù)。
要點(diǎn)
bytes包含的是由8位值所組成的序列,str包含的是由Unicode碼點(diǎn)所組成的序列。
我們可以編寫(xiě)輔助函數(shù)來(lái)確保程序收到的字符序列確實(shí)是期望要操作的類型(要知道自己想操作的到底是Unicode碼點(diǎn),還是原始的8位值。用UTF-8標(biāo)準(zhǔn)給字符串編碼,得到的就是這樣的一系列8位值)。
bytes與str這兩種實(shí)例不能在某些操作符(例如>、==、+、%操作符)上面混用。
從文件中讀取二進(jìn)制數(shù)據(jù)(或者把二進(jìn)制數(shù)據(jù)寫(xiě)入文件)時(shí),應(yīng)該用'rb'('wb')這樣的二進(jìn)制模式打開(kāi)文件。
如果要從文件中讀取(或者要寫(xiě)入文件之中)的是Unicode數(shù)據(jù),那么必須注意系統(tǒng)默認(rèn)的文本編碼方案。若無(wú)法肯定,可通過(guò)encoding參數(shù)明確指定。
以上內(nèi)容為大家介紹了如何理解Python中bytes與str的區(qū)別,希望對(duì)大家有所幫助,如果想要了解更多Python相關(guān)知識(shí),請(qǐng)關(guān)注IT培訓(xùn)機(jī)構(gòu):千鋒教育。http://m.2667701.com/