欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

python使用struct模塊實現(xiàn)打包/解包二進制數(shù)據(jù)

 更新時間:2023年09月12日 08:56:47   作者:古明地覺的編程教室  
因為網(wǎng)絡傳輸?shù)臄?shù)據(jù)都是二進制字節(jié)流,而?Python?只有字符串可以直接轉(zhuǎn)成字節(jié)流,對于整數(shù)、浮點數(shù)則無能為力了,所以?Python?提供了?struct?模塊來幫我們解決這一點,下面我們就來看看它的用法吧

Python 有一個內(nèi)置模塊 struct,從名字上看這和 C 的結(jié)構(gòu)體有著千絲萬縷的聯(lián)系,C 的結(jié)構(gòu)體是由多個數(shù)據(jù)組合而成的一種新的數(shù)據(jù)類型。

typedef?struct?{
????char?*name;
????int?age;
????char?*?gender;
????long?salary;
}?People;

struct 模塊也是負責將多個不同類型的數(shù)據(jù)組合在一起,因為網(wǎng)絡傳輸?shù)臄?shù)據(jù)都是二進制字節(jié)流。而 Python 只有字符串可以直接轉(zhuǎn)成字節(jié)流,對于整數(shù)、浮點數(shù)則無能為力了。所以 Python 提供了 struct 模塊來幫我們解決這一點,下面來看看它的用法。

打包和解包

struct 模塊內(nèi)部有兩個函數(shù)用于打包和解包,分別是 pack 和 unpack。

  • pack:將數(shù)據(jù)打包成二進制字節(jié)流;
  • unpack:對二進制字節(jié)流進行解包;
import?binascii
import?struct
# values 包含一個 12 字節(jié)的字節(jié)串、一個整數(shù)、以及一個浮點數(shù)。
values?=?("古明地覺".encode("utf-8"),?17,?156.7)
#?第一個參數(shù)?"12s?i?f"?表示格式化字符串(format),里面的符號則代表數(shù)據(jù)的類型
# 12s:12 個字節(jié)的字節(jié)串、i:?整數(shù)、f:?浮點數(shù)
#?因此 12s i f 表示打包的數(shù)據(jù)有三個,分別是:12 個字節(jié)的字節(jié)串、一個整數(shù)、以及一個浮點數(shù)
#?中間使用的空格只是用來對表示類型的符號進行分隔,在編譯時會被忽略
packed_data?=?struct.pack("12s?i?f",?*values)??#?這里需要使用?*?將元組打開
#?查看打印的結(jié)果
print(packed_data)
"""
b'\xe5\x8f\xa4\xe6\x98\x8e\xe5\x9c\xb0\xe8\xa7\x89\x11\x00\x00\x003\xb3\x1cC'
"""
#?還可以將打包后的結(jié)果轉(zhuǎn)成十六進制,?這樣傳輸起來更加方便
print(binascii.hexlify(packed_data))
"""
b'e58fa4e6988ee59cb0e8a7891100000033b31c43'
"""

代碼中的 packed_data 就是打包之后的結(jié)果,而我們又將其轉(zhuǎn)成了 16 進制表示。那么問題來了,既然能打包,那么肯定也能解包。

import?struct
import?binascii
#?之前對打包之后的數(shù)據(jù)轉(zhuǎn)成?16?進制所得到的結(jié)果
data?=?b'e58fa4e6988ee59cb0e8a7891100000033b31c43'
#?所以可以使用?binascii.unhexlify?將其轉(zhuǎn)回來,得到?struct?打包之后的數(shù)據(jù)
packed_data?=?binascii.unhexlify(data)
#?然后調(diào)用?struct.unpack?進行解包,打包用的什么格式,解包也用什么格式
#?會得到一個元組,哪怕解包之后只有一個元素,得到的也是元組
values?=?struct.unpack("12s?i?f",?packed_data)
print(str(values[0],?encoding="utf-8"))??#?古明地覺
print(values[1])??#?17
print(values[2])??#?156.6999969482422

發(fā)送端將數(shù)據(jù)按照某種格式轉(zhuǎn)成二進制字節(jié)流,接收端在接收到數(shù)據(jù)之后再按照相同的格式轉(zhuǎn)成相應的數(shù)據(jù)就行。只不過 Python 中,只有字符串可以直接轉(zhuǎn)換成二進制字節(jié)流,整數(shù)、浮點數(shù)則需要借助于 struct 模塊。

但是注意:在使用 struct 打包的時候,不能直接對字符串打包,而是需要先將字符串編碼成bytes對象。因為中文字符采用不同的編碼所占的字節(jié)數(shù)不同,所以需要先手動編碼成 bytes 對象。

整體還是比較簡單的,就是將數(shù)據(jù)按照指定格式進行打包,然后再按照指定格式進行解包。而像 12s、i、f 這些都屬于我們定義的格式中的類型指示符,而除了這些指示符之外,還有哪些類型指示符呢?

然后需要注意,我們在進行打包的時候,類型以及個數(shù)一定要匹配。

import?struct
try:
????struct.pack("iii",?1,?2,?3.14)
except?Exception?as?e:
????print(e)??#?required?argument?is?not?an?integer
#?告訴我們需要的是整數(shù),?但我們傳遞了浮點數(shù)
try:
????#?iii?表示接收?3?個整數(shù),?但我們只傳遞了兩個
????struct.pack("iii",?1,?2)
except?Exception?as?e:
????print(e)??#?pack?expected?3?items?for?packing?(got?2)
try:
????#?iii?表示接收?3?個整數(shù),?但我們卻傳遞了四個
????struct.pack("iii",?1,?2,?3,?4)
except?Exception?as?e:
????print(e)??#?pack?expected?3?items?for?packing?(got?4)

此外,我們之前說一個長度為 12 的字節(jié)串,可以使用 12s 來表示,那么 3s 就表示長度為 3 的字節(jié)串。問題來了,i 表示整數(shù),那么3i 表示什么呢?

import?struct
try:
????struct.pack("3i",?1,?2)
except?Exception?as?e:
????print(e)??#?pack?expected?3?items?for?packing?(got?2)
#?告訴我們接收?3?個值,?但是只傳遞了兩個
packed_data?=?struct.pack("3i",?1,?2,?3)
print(struct.unpack("3i",?packed_data))??#?(1,?2,?3)

我們看到 3i 在結(jié)果上等同于 iii,但對于 s 而言,3s 可不等同于 sss。3s 仍然表示接收一個元素,只不過這個元素是字節(jié)串,并且長度為 3。這些細節(jié)要注意。

當然對于字符串而言,即使長度不一樣也是無所謂的,我們舉個例子。

import?struct
#?第一個值是整數(shù),?第二個值是字節(jié)串(長度應該為3,?但不是3也可以)
packed_data?=?struct.pack("i3s",?6,?b"abcdefg")
print(packed_data)??#?b'\x06\x00\x00\x00abc'
#?我們看到被截斷了,?只剩下了?abc
packed_data?=?struct.pack("i6s",?6,?b"abc")
print(packed_data)??#?b'\x06\x00\x00\x00abc\x00\x00\x00'
#?6s?需要字節(jié)長度為?6,?但是我們只有三個,?所以在結(jié)尾補上了?3?個?\0

總之在使用 struct 進行打包的時候,需要記住兩點:

  • 元素個數(shù)和符號個數(shù)要對應, 比如 3i3s3f 表示接收 7 個元素,依次是 3 個整數(shù)、一個字節(jié)串、3 個浮點數(shù);
  • 元素類型和符號要對應,比如 i 對應整數(shù),s 對應字節(jié)串等等;并且對于 s 而言,前面的數(shù)字表示接收的字節(jié)串的長度;

而對于解包而言,我們也需要關注,但只需要關注一點,那就是大小。怎么理解呢?來舉個例子:

import?struct
packed_data?=?struct.pack("ii",?1,?2)
print(packed_data)??#?b'\x01\x00\x00\x00\x02\x00\x00\x00'
#?因為 i 表示 C 的 int, 而 C 的一個 int 占 4 字節(jié), 所以結(jié)果是 8 字節(jié)。
#?只不過?1?和?2?只需一字節(jié)即可存儲,?因此其它的部分都是?0
#?打包之后的?packed_data?的大小,?不取決于打包的元素,?而是取決于格式化字符串中的類型符號
#?比如?struct.pack("4s",?b"abc")?,?盡管傳遞的字節(jié)串只有?3?字節(jié)
#?但指定的是?4s,?所以打包之后的?packed_data?占?4?字節(jié)
#?而我們在解包的時候,?指定的符號的字節(jié)大小?和?packed_data?要匹配
#?比如這里的?packed_data?是?8?字節(jié),?在打包結(jié)束之后它的大小就已經(jīng)固定了
try:
????print(struct.unpack("i",?packed_data))
except?Exception?as?e:
????print(e)??#?unpack?requires?a?buffer?of?4?bytes
#?告訴我們需要一個?4?字節(jié)的buffer,?這是因為我們的?packed_data?是?8?字節(jié)
#?同理:
try:
????print(struct.unpack("iii",?packed_data))
except?Exception?as?e:
????print(e)??#?unpack?requires?a?buffer?of?12?bytes
#?這樣也是不可以的,?告訴我們需要?12?字節(jié)
#?只有字節(jié)數(shù)匹配,?才可以正常解析
print(struct.unpack("ii",?packed_data))??#?(1,?2)

那么問題來了,我們說一個 long long 是占 8 字節(jié),正好對應兩個 int,那么將兩個 int 按照一個 long long 來解析可不可以呢?再有 8s 也是 8 字節(jié),又可不可以進行解析呢?我們來試一下。

import?struct
packed_data?=?struct.pack("ii",?1,?2)
print(packed_data)
"""
b'\x01\x00\x00\x00\x02\x00\x00\x00'
"""
print(struct.unpack("8s",?packed_data))
"""
(b'\x01\x00\x00\x00\x02\x00\x00\x00',)
"""
print(struct.unpack("q",?packed_data))
"""
(8589934593,)
"""
#?答案顯然是可以的,?因為字節(jié)數(shù)是相匹配的
#?對于?8s?而言,?我們看到解析出來的結(jié)果就是原始的字節(jié)流
#?對于?q,?也可以正確解析,?只不過結(jié)果不是我們想要的
#?但是我們觀察一下按照?q?解析出來的結(jié)果,?結(jié)果是?8589934593,?那么它是怎么得到的呢?
#?如果將?packet_data?按照?8?字節(jié)整數(shù)解析,相當于將兩個?4?字節(jié)整數(shù)合并成一個?8?字節(jié)整數(shù)
#?其中整數(shù)?2?占據(jù)前?32?個位,整數(shù)?1?占據(jù)后?32?個位
print((2?<<?32)?+?1)
"""
8589934593
"""
#?怎么樣,?結(jié)果是不是一樣呢??至于這里為什么不是?(1?<<?31)?+?2,?我們后面會說

所以在解析的時候,格式化字符串中的類型符號對應的字節(jié)數(shù),要和 packed_data 的字節(jié)數(shù)相匹配,這是不報錯的前提。當然如果想得到正確的結(jié)果,最關鍵的還是解包對應的格式化字符串,要和打包對應的格式化字符串保持一致。

struct.Struct

在 struct 模塊中,我們可以直接使用 struct.pack 和 struct.unpack 這兩個模塊級的函數(shù),但是 struct 模塊還提供了一個 Struct 類。

import?struct
s?=?struct.Struct("ii")
#?和使用?struct.pack("ii",?1,?2)?之間是等價的
packed_data?=?s.pack(1,?2)?
print(packed_data)??
"""
b'\x01\x00\x00\x00\x02\x00\x00\x00'
"""

如果我們需要使用同一種格式化字符串來對大量數(shù)據(jù)進行打包的話,那么使用 struct.Struct 是更推薦的,可以類比正則。

re.search(pattern, string) 這個過程分為兩步,會先將 pattern 進行編譯轉(zhuǎn)換,然后再進行匹配。如果我們需要同一個 pattern 匹配 100 個字符串的話,那么要編譯轉(zhuǎn)換 100 次。

而如果先對 pattern 進行編譯 comp = re.compile(pattern),那么不管調(diào)用 comp.search(string) 多少次,都只會進行一次編譯轉(zhuǎn)換,效率會更高。struct 也是類似的,如果要按照相同的格式進行多次打包,那么創(chuàng)建一個 Struct 實例并在這個實例上調(diào)用方法時(不使用模塊級函數(shù))會更高效。

當然,使用 Struct 類還有一個好處,就是可以獲取一些額外信息。

import?struct
s?=?struct.Struct("ii4sf")
print("格式化字符串:",?s.format)??#?格式化字符串:?ii4sf
print("字節(jié)數(shù):",?s.size)??#?字節(jié)數(shù):?16

我們看到打包后的數(shù)據(jù)大小是由格式化字符串中的符號所決定的。

字節(jié)序

說到字節(jié)序,你應該會想到大端存儲、小端存儲,所謂大端存儲就是:數(shù)據(jù)的低位存儲在內(nèi)存的高地址中,高位存儲在內(nèi)存的低地址中。而小端存儲與之相反:數(shù)據(jù)的低位存儲在內(nèi)存的低地址中,高位存儲在內(nèi)存的高地址中。

那么 Python 的 struct 默認使用什么存儲呢?答案是小端存儲。

import?struct
#?i?表示?int32,那么相應的整數(shù)?1?就占?4?字節(jié)
#?其中最低位存儲的是?1,剩余三個位存儲的是?0
packed_data?=?struct.pack("i",?1)
print(packed_data)??#?b'\x01\x00\x00\x00'
#?打包之后的數(shù)據(jù)是一個字節(jié)串,或者理解為?C?的字符數(shù)組
#?而數(shù)組的元素,從左往右對應的內(nèi)存地址是依次增大的
#?所以結(jié)果就是低位存在了低地址中,所以這里是小端存儲
#?而如果想要變成大端存儲的話,?可以這么做
packed_data?=?struct.pack(">i",?1)
print(packed_data)??#?b'\x00\x00\x00\x01'
#?我們看到此時結(jié)果就變了

當然我們在解析的時候也需要注意大小端的問題,如果是打包的時候使用的是大端存儲,那么解包的時候也要使用大端存儲。

import?struct
#?因為?\x01?在最后面,而后面表示內(nèi)存的高地址
#?此時這個數(shù)字如果想表示?1,?那么它一定是大端存儲的?1
#?也就是將低位的?\x01?放在了高地址中
packed_data?=?b'\x00\x00\x00\x01'
#?這里我們不指定大小端,?默認是小端
print(struct.unpack("i",?packed_data))
"""
(16777216,)
"""
#?我們看到結(jié)果變了,?至于這個結(jié)果怎么來的,?很簡單
#?無論打包還是解包,如果不指定字節(jié)序,那么默認都是小端,低位存在低地址中
#?所以?b'\x00\x00\x00\x01'?等價于如下
print(0b00000001_00000000_00000000_00000000)?
"""
16777216
"""
#?所以我們也要用大端存儲進行解析,?表示:?我是大端存儲,?存儲在高地址的是數(shù)據(jù)的低位
print(struct.unpack(">i",?packed_data))?
"""
(1,)
"""
print(0b00000000_00000000_00000000_00000001)??
"""
1
"""
#?所以通過?b'\x00\x00\x00\x01'?的值是不是 1,可以判斷當前采用的是大端還是小端
#?因為 \x01 在高地址,如果值為 1,說明 \x01 是低位,因此是大端,否則小端

然后再回顧一下之前的例子,我們用一個 long long 表示兩個 int。

import?struct
packed_data?=?struct.pack("ii",?1,?2)
print(packed_data)
"""
b'\x01\x00\x00\x00\x02\x00\x00\x00'
"""
#?將兩個?int?轉(zhuǎn)成一個?long?long,默認是小端,所以低位存在低地址中
#?因此?\x01\x00\x00\x00?表示?1,占據(jù)低?32?個位
#?\x02\x00\x00\x00?表示?2,占據(jù)高?32?個位
print(struct.unpack("q",?packed_data))
print((2?<<?32)?+?1)
"""
(8589934593,)
8589934593
"""
#?如果按照大端解析的話,低位存在高地址中,那么就是相反的
#?但此時?\x01\x00\x00\x00?表示的不再是?1
#?同理?\x02\x00\x00\x00?表示也不再是?2
print(struct.unpack(">q",?packed_data))
print(
????(0b00000001_00000000_00000000_00000000?<<?32)?+
????0b00000010_00000000_00000000_00000000
)
"""
(72057594071482368,)
72057594071482368
"""

因此 struct 模塊給我們提供了自定義字節(jié)序的功能,可以顯式地指定是使用大端存儲、還是小端存儲。而方法也很簡單,只需要給格式化字符串的第一個字符指定為特定的符號即可實現(xiàn)這一點。

  • > : 大端字節(jié)序(Big-endian)
  • < : 小端字節(jié)序(Little-endian)
  • ! : 網(wǎng)絡字節(jié)序(實際上就是大端字節(jié)序)
  • = : 本地字節(jié)序(當前系統(tǒng)的字節(jié)序)
  • @ : 本地字節(jié)序(和 = 相同,但可能有不同的對齊方式)

緩沖區(qū)

pack 方法在打包的時候,會為打包數(shù)據(jù)申請一塊內(nèi)存空間,也就是說每一次 pack 都需要申請內(nèi)存資源,顯然這是一種浪費。通過避免為每個打包數(shù)據(jù)分配一個新緩沖區(qū),在內(nèi)存開銷上可以得到優(yōu)化。而 pack_into 和 pack_from 可以支持我們從指定的緩沖區(qū)進行讀取和寫入。

import?struct
import?ctypes
#?創(chuàng)建一個?string?緩存,?大小為?10
buf?=?ctypes.create_string_buffer(10)
#?raw?表示原始數(shù)據(jù),這里都是?\0,因為?C?中是通過?\0?來標識一個字符串的結(jié)束
print(buf.raw)??#?b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
#?value?就是?Python?中的字符串,?顯然為空
print(buf.value)??#?b''
#?然后我們進行打包,?第二個參數(shù)表示緩沖區(qū)
#?第三個參數(shù)表示偏移量,?0表示從頭開始寫入;?然后后面的參數(shù)就是打包的數(shù)據(jù)
struct.pack_into("ii2s",?buf,?0,?123,?345,?b"ab")
#?打包之后的數(shù)據(jù)會存在?buf?中,解包的話,使用 unpack_from
#?會從?buf?中讀取數(shù)據(jù)并解析,第三個參數(shù)表示從偏移量為 0 的位置開始解析
values?=?struct.unpack_from("ii2s",?buf,?0)
print(values)??#?(123,?345,?b'ab')

這里的 pack_into 不會產(chǎn)生新的內(nèi)存空間,都是對 buf 進行操作。另外我們還看到了偏移量,所以可以將多個打包的數(shù)據(jù)寫入到同一個 buf 中,然后也可以從同一個 buf 中進行解包,而保證數(shù)據(jù)不沖突的前提正是這里的偏移量,舉個栗子:

import?struct
import?ctypes
s1?=?struct.Struct("ii6si")
s2?=?struct.Struct("2s")
buf?=?ctypes.create_string_buffer(s1.size?+?s2.size)
s1.pack_into(buf,?0,?1,?2,?b"abcdef",?3)
#?偏移量為?s1.size
s2.pack_into(buf,?s1.size,?b"gh")
#?從?si.size?開始解析
print(s2.unpack_from(buf,?s1.size))??#?(b'gh',)
#?從?0?開始解析,解析?s1.size?個字節(jié)
print(s1.unpack_from(buf,?0))??#?(1,?2,?b'abcdef',?3)

以上就是 struct 模塊,它定義了Python中整數(shù)、浮點數(shù)和二進制流之間的通用轉(zhuǎn)換邏輯。

到此這篇關于python使用struct模塊實現(xiàn)打包/解包二進制數(shù)據(jù)的文章就介紹到這了,更多相關python struct內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Python抽象基類的定義與使用方法

    Python抽象基類的定義與使用方法

    這篇文章主要介紹了Python抽象基類的定義與使用方法,Python的抽象基類是指必須讓繼承它的子類去實現(xiàn)它所要求的抽象方法的類,下面文章內(nèi)容將詳細介紹相關資料,需要的朋友可以參考一下
    2021-10-10
  • Python爬蟲:將headers請求頭字符串轉(zhuǎn)為字典的方法

    Python爬蟲:將headers請求頭字符串轉(zhuǎn)為字典的方法

    今天小編就為大家分享一篇Python爬蟲:將headers請求頭字符串轉(zhuǎn)為字典的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-08-08
  • pymysql.err.DataError:1366的報錯解決

    pymysql.err.DataError:1366的報錯解決

    通過python把數(shù)據(jù)同步至mysql數(shù)據(jù)庫的過程中,遇到錯誤,本文主要介紹了pymysql.err.DataError:1366的報錯解決,具有一定的參考價值,感興趣的可以了解一下
    2024-05-05
  • python:列表詳解

    python:列表詳解

    這篇文章主要介紹了Python中列表(List)的詳解操作方法,包含創(chuàng)建、訪問、更新、刪除、其它操作等,需要的朋友可以參考下
    2021-10-10
  • Python?類方法和靜態(tài)方法之間的區(qū)別

    Python?類方法和靜態(tài)方法之間的區(qū)別

    這篇文章主要介紹了Python?類方法和靜態(tài)方法之間的區(qū)別,靜態(tài)方法并不是真正意義上的類方法,它只是一個被放到類里的函數(shù)而已,更多內(nèi)容需要的朋友可以參考一下
    2022-07-07
  • 聊聊Numpy.array中[:]和[::]的區(qū)別在哪

    聊聊Numpy.array中[:]和[::]的區(qū)別在哪

    這篇文章主要介紹了在Numpy.array中[:]和[::]的區(qū)別說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-05-05
  • 如何利用Boost.Python實現(xiàn)Python C/C++混合編程詳解

    如何利用Boost.Python實現(xiàn)Python C/C++混合編程詳解

    這篇文章主要給大家介紹了關于如何利用Boost.Python實現(xiàn)Python C/C++混合編程的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起看看吧
    2018-11-11
  • python中map的基本用法示例

    python中map的基本用法示例

    map函數(shù)的原型是map(function, iterable, …),它的返回結(jié)果是一個列表。下面這篇文章主要給大家介紹了關于python中map的基本用法,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考下
    2018-09-09
  • python對矩陣進行轉(zhuǎn)置的2種處理方法

    python對矩陣進行轉(zhuǎn)置的2種處理方法

    這篇文章主要介紹了python對矩陣進行轉(zhuǎn)置的2種處理方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-07-07
  • Python實現(xiàn)對比不同字體中的同一字符的顯示效果

    Python實現(xiàn)對比不同字體中的同一字符的顯示效果

    這篇文章主要介紹了Python實現(xiàn)對比不同字體中的同一字符的顯示效果,也就是對比不同字體中某個字的顯示效果,這在做設計時非常有用,需要的朋友可以參考下
    2015-04-04

最新評論