【Python】中的bytes問題

bytes是什么

由上篇文章幾種字符編碼, 我們已經(jīng)知道了ASCII Unicode UTF-8的關(guān)系。而且,計(jì)算機(jī)只能識(shí)別0和1,那顯然,文件存儲(chǔ)在計(jì)算機(jī)中也只能是以二進(jìn)制的形式存儲(chǔ),字符編碼在計(jì)算機(jī)中的工作機(jī)制是怎樣的呢?

在計(jì)算機(jī)內(nèi)存中(你打開電腦上的一個(gè)文件是要從硬盤讀取到內(nèi)存中的),統(tǒng)一使用Unicode編碼。在需要保存到硬盤或需要傳輸時(shí),就轉(zhuǎn)化為UTF-8編碼(由上篇文章可知,這樣可以節(jié)省空間,提高傳輸速度)。

如,在記事本編輯時(shí),從文件讀取的UTF-8字符被轉(zhuǎn)化為Unicode字符到內(nèi)存里,編輯完成,保存時(shí)在將內(nèi)存中的Unicode字符轉(zhuǎn)化為UTF-8保存到文件:

mark

瀏覽網(wǎng)頁時(shí),服務(wù)器會(huì)把動(dòng)態(tài)生成的Unicode字符轉(zhuǎn)化為UTF-8字符再傳輸?shù)綖g覽器:

mark

所以你看到很多網(wǎng)頁的源碼上會(huì)有類似<meta charset="UTF-8" />的信息,表示該網(wǎng)頁正在使用UTF-8編碼。

在python中,字符串是以Unicode編碼的,而python的字符串類型是str,內(nèi)存中以Unicode表示。要在網(wǎng)絡(luò)上進(jìn)行傳輸或保存到磁盤中,就需要將str轉(zhuǎn)化為以字節(jié)為單位的bytes。

要獲取字符的bytes表示,可以使用encode()方法,如

>>> 'ABC'.encode('ascii')
b'ABC'
>>>'ABC'.encode('utf-8')
b'ABC'
>>> '中文'.encode('utf-8')
b'\xe4\xb8\xad\xe6\x96\x87'
>>> '中文'.encode('ascii')
Traceback (most recent call last):
  File "<input>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)

純英文的str,可以用ascii編碼為bytes,其內(nèi)容與utf-8相同。含中文的str不能用ascii編碼為bytes,其超過了ascii編碼的范圍,會(huì)報(bào)錯(cuò)。

bytes中,無法顯示為ASCII字符的字節(jié),會(huì)以b\x##的形式顯示。

使用type可以查看b'abc'或b'\xe4\xb8\xad\xe6\x96\x87'的數(shù)據(jù)類型,是一個(gè)bytes類

>>> type(b'\xe4\xb8\xad\xe6\x96\x87')
<class 'bytes'>

相反,從網(wǎng)絡(luò)上或磁盤中讀取到了字節(jié)流,讀到的就是bytes,需要用decode()方法解碼為str

>>> b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
'中文'

字節(jié)字符串與文本字符串

b'ABC' 與 'ABC'是不同的,前者是bytes,也叫字節(jié)字符串;后者是str,也稱為文本字符串。前者一個(gè)字符占一個(gè)字節(jié)(中文一個(gè)漢字占三個(gè)字節(jié)),str類型在內(nèi)存中以Unicode表示,一個(gè)字符占若干字節(jié)。

>>> len('下'.encode('utf-8'))
3

在讀取二進(jìn)制數(shù)據(jù)的時(shí)候,字節(jié)字符串和文本字符串可能會(huì)引起錯(cuò)誤。特別需要注意的是,索引和迭代動(dòng)作返回的是字節(jié)的值而不是字節(jié)字符串。

>>> # Text string
>>> t = 'Hello world'
>>> for x in t:
...     print(x)
...     
H
e
l
l
o
w
o
r
l
d
>>> # Byte string
>>> b = b'Hello world'
>>> for x in b:
...     print(x)
...     
72
101
108
108
111
32
119
111
114
108
100

Base64:顯示與打印二進(jìn)制數(shù)據(jù)

Base64是一種用64個(gè)字符表示任意二進(jìn)制數(shù)據(jù)的方法。

當(dāng)我們用記事本打開bmp, exe, jpg文件時(shí),會(huì)出現(xiàn)一大堆亂碼:

mark

這是因?yàn)樗鼈儾皇俏谋疚募?,是二進(jìn)制文件,而二進(jìn)制文件包含很多無法顯示和打印的字符,所以,如果想讓記事本這樣的文本處理軟件能處理二進(jìn)制數(shù)據(jù),就需要一個(gè)二進(jìn)制到字符的轉(zhuǎn)換方法,Base64是一種最常見的二進(jìn)制編碼方法。

注意:通常我們說編碼都是將字符編碼成二進(jìn)制,將二進(jìn)制解碼為字符。而現(xiàn)在我們說的是將二進(jìn)制編碼為字符文本,將字符文本解碼為二進(jìn)制。不要弄混,筆者在最開始學(xué)的時(shí)候全程懵逼,完全搞不明白到底是在解碼還是編碼。

方法:

準(zhǔn)備一個(gè)包含64個(gè)字符的數(shù)組:

['A', 'B', 'C', ... 'a', 'b', 'c', ... '0', '1', ... '+', '/']

對(duì)二進(jìn)制數(shù)據(jù)進(jìn)行處理,每三個(gè)字節(jié)一組,共3 * 8 = 24bit,劃為4組,每組6個(gè)bit:

mark

我們得到四個(gè)數(shù)字作為索引,查表,得到對(duì)應(yīng)的4個(gè)字符,就是編碼后的字符串。

所以,我們是將3個(gè)字節(jié)的二進(jìn)制數(shù)據(jù)編碼為4個(gè)字節(jié)的文本數(shù)據(jù),長(zhǎng)度增加33%,好處是編碼后的文本可以在郵件正文、網(wǎng)頁中正常顯示。

python內(nèi)置的base64模塊可以提供base64的編解碼功能:

>>> import base64
>>> base64.b64encode(b'i\xb7\x1d\xfb\xef\xff')
b'abcd++//'
>>> base64.b64decode(b'abcd++//')
b'i\xb7\x1d\xfb\xef\xff'

當(dāng)要編碼的二進(jìn)制數(shù)據(jù)不是3的倍數(shù),最后會(huì)剩下1或2個(gè)字符時(shí)怎么辦?Base64會(huì)自動(dòng)在末尾用b\x00補(bǔ)足后在進(jìn)行解碼,再在編碼的結(jié)尾加上1或2個(gè)=,以表示在二進(jìn)制數(shù)據(jù)末尾加了幾個(gè)b\x00。

但是,在很多Base64編碼中會(huì)把=去掉,因?yàn)樗鼤?huì)在URL,Cookies中造成歧義:

# 標(biāo)準(zhǔn)Base64:
'abcd' -> 'YWJjZA=='
# 自動(dòng)去掉=:
'abcd' -> 'YWJjZA'

去掉=怎么解碼呢,因?yàn)锽ase64編碼后的長(zhǎng)度永遠(yuǎn)是4的整數(shù)倍,所以將不是4的整數(shù)倍的Base編碼自動(dòng)添加相應(yīng)數(shù)量=后使其變?yōu)?的整數(shù)倍后再解碼即可

你可能會(huì)想,上面我們已經(jīng)說了可以用decode解碼二進(jìn)制數(shù)據(jù),為什么現(xiàn)在還需要對(duì)二進(jìn)制數(shù)據(jù)編碼再顯示呢?

原因是:上文針對(duì)的是文本文件中的字符,而像jpg bmp mp3等二進(jìn)制格式文件,其中的二進(jìn)制數(shù)據(jù)不能正常解析為字符:

>>> '中文'.encode('utf-8')
b'\xe4\xb8\xad\xe6\x96\x87'
>>> b'\xe4\xb8\xad\xe6\x96\x56\x87'.decode('utf-8')
Traceback (most recent call last):
  File "<input>", line 1, in <module>
UnicodeDecodeError: 'utf-8' codec can't decode bytes in position 3-4: invalid continuation byte

可很多時(shí)候我們會(huì)在一些文件頭加一些與文件屬性有關(guān)的數(shù)據(jù),如在jpg文件頭加數(shù)據(jù)表示該圖片的大小、分辨率、色彩等信息,這時(shí)我們就需要通過對(duì)二進(jìn)制進(jìn)行編碼讀取這些信息了。

struct bytes與其他數(shù)據(jù)類型的轉(zhuǎn)換

Bytes之間可以進(jìn)行加法(無減法操作)組成一個(gè)新的bytes:

>>> m = b'hello '
>>> b = b'world'
>>> m+b
b'hello world'
>>> m+b'world'
b'hello world'

根據(jù)前文已知,將字符轉(zhuǎn)換成二進(jìn)制可以使用encode()方法,那如果是非字符型數(shù)據(jù)如整數(shù)、浮點(diǎn)數(shù)怎么轉(zhuǎn)換成二進(jìn)制數(shù)據(jù)呢?

Python提供了一個(gè)struct模塊來解決bytes與其他數(shù)據(jù)類型之間的轉(zhuǎn)換:

>>> struct.pack('>I', 10240099)
b'\x00\x9c@c'

pack第一個(gè)參數(shù)是處理指令,'>I'的意思是:

>表示字節(jié)順序是big-endian,也就是網(wǎng)絡(luò)序,I表示4字節(jié)無符號(hào)整數(shù),unsigned int。

后面參數(shù)個(gè)數(shù)要與處理指令一致,大小也要在指定的參數(shù)范圍內(nèi):

>>> struct.pack('>2H', 10245599)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
struct.error: pack expected 2 items for packing (got 1)

>>> struct.pack('>2H', 102456565599)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
struct.error: pack expected 2 items for packing (got 1)

H表示整數(shù),兩字節(jié)無符號(hào)整數(shù),usigned short。

struct模塊定義的數(shù)據(jù)類型可以參考python的官方文檔

mark

相反,unpack指令就用來將字節(jié)流bytes按給定參數(shù)轉(zhuǎn)化為我們想要的格式:

>>> struct.unpack('>I', b'\x00\x9c@c')
(10240099,)

該句指令的意思是:將給定的字節(jié)流轉(zhuǎn)換成unsigned int類型的4字節(jié)無符號(hào)整數(shù)。unpack同樣也可以將字節(jié)流轉(zhuǎn)換為字符數(shù)據(jù),更換參數(shù)即可。

unpack返回的是tuple類型

應(yīng)用場(chǎng)景

有時(shí)需要用python處理二進(jìn)制數(shù)據(jù),比如存取文件,socket操作時(shí)。這時(shí)可以用python的struct模塊來完成,比如可以用struct處理c語言中的結(jié)構(gòu)體。

比如有一個(gè)結(jié)構(gòu)體:

struct Header
{
    unsigned short id;
    char[4] tag;
    unsigned int version;
    unsigned int count;
}

通過socket.recv接收到了上面的結(jié)構(gòu)體數(shù)據(jù),存在字符串s中,bytes格式,現(xiàn)在把它解析出來,可以使用unpack函數(shù):

import struct
id, tag, version, count = struct.unpack('!H4s2I', s)

!表示網(wǎng)絡(luò)字節(jié)順序,因?yàn)閿?shù)據(jù)是從網(wǎng)絡(luò)上接收到的,再網(wǎng)絡(luò)上傳送時(shí)他是網(wǎng)絡(luò)字節(jié)順序的。后面的H4s2I表示1個(gè)unsigned int,4s表示4字節(jié)的字符串,2個(gè)unsigned short。

通過一個(gè)unpack就將id, tag, version, count數(shù)據(jù)解析好了。

同樣,也可以使用pack再將本地?cái)?shù)據(jù)pack成struct格式

ss = struct.pack('>I4s2I', id, tag, version, count)

pack函數(shù)按照指定格式轉(zhuǎn)換成了結(jié)構(gòu)體Header,ss現(xiàn)在是一個(gè)字節(jié)流,可以通過socket將這個(gè)字節(jié)流發(fā)送出去

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,494評(píng)論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評(píng)論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,714評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,186評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,410評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,940評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,776評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,976評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,210評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評(píng)論 1 286
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,654評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,958評(píng)論 2 373

推薦閱讀更多精彩內(nèi)容

  • 前言 最先接觸編程的知識(shí)是在大學(xué)里面,大學(xué)里面學(xué)了一些基礎(chǔ)的知識(shí),c語言,java語言,單片機(jī)的匯編語言等;大學(xué)畢...
    oceanfive閱讀 3,117評(píng)論 0 7
  • 編碼問題一直困擾著開發(fā)人員,尤其在 Java 中更加明顯,因?yàn)?Java 是跨平臺(tái)語言,不同平臺(tái)之間編碼之間的切換...
    x360閱讀 2,492評(píng)論 1 20
  • 字符集和編碼簡(jiǎn)介 在編程中常??梢砸姷礁鞣N字符集和編碼,包括ASCII,MBCS,Unicode等字符集。確切的說...
    蘭山小亭閱讀 8,541評(píng)論 0 13
  • 說明:本文是我在readthedocs看到的,覺得很不錯(cuò)所以轉(zhuǎn)載過來,有刪改,原文地址點(diǎn)這里。 實(shí)用Unicode...
    aurora閱讀 996評(píng)論 0 6
  • 我是一個(gè)曾經(jīng)被嚴(yán)重失眠困擾了一年零三個(gè)月的人。 嚴(yán)重到什么程度呢? 很困,很困很困,眼睛完全睜不開,腦子混沌不清,...
    奧尼恩小姐閱讀 826評(píng)論 0 49