最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

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

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

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

打包和解包

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

  • pack:將數(shù)據打包成二進制字節(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ù)據的類型
# 12s:12 個字節(jié)的字節(jié)串、i:?整數(shù)、f:?浮點數(shù)
#?因此 12s i f 表示打包的數(shù)據有三個,分別是:12 個字節(jié)的字節(jié)串、一個整數(shù)、以及一個浮點數(shù)
#?中間使用的空格只是用來對表示類型的符號進行分隔,在編譯時會被忽略
packed_data?=?struct.pack("12s?i?f",?*values)??#?這里需要使用?*?將元組打開
#?查看打印的結果
print(packed_data)
"""
b'\xe5\x8f\xa4\xe6\x98\x8e\xe5\x9c\xb0\xe8\xa7\x89\x11\x00\x00\x003\xb3\x1cC'
"""
#?還可以將打包后的結果轉成十六進制,?這樣傳輸起來更加方便
print(binascii.hexlify(packed_data))
"""
b'e58fa4e6988ee59cb0e8a7891100000033b31c43'
"""

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

import?struct
import?binascii
#?之前對打包之后的數(shù)據轉成?16?進制所得到的結果
data?=?b'e58fa4e6988ee59cb0e8a7891100000033b31c43'
#?所以可以使用?binascii.unhexlify?將其轉回來,得到?struct?打包之后的數(shù)據
packed_data?=?binascii.unhexlify(data)
#?然后調用?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ù)據按照某種格式轉成二進制字節(jié)流,接收端在接收到數(shù)據之后再按照相同的格式轉成相應的數(shù)據就行。只不過 Python 中,只有字符串可以直接轉換成二進制字節(jié)流,整數(shù)、浮點數(shù)則需要借助于 struct 模塊。

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

整體還是比較簡單的,就是將數(shù)據按照指定格式進行打包,然后再按照指定格式進行解包。而像 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 在結果上等同于 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,?但是我們只有三個,?所以在結尾補上了?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é), 所以結果是 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é),?在打包結束之后它的大小就已經固定了
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é)流
#?對于?q,?也可以正確解析,?只不過結果不是我們想要的
#?但是我們觀察一下按照?q?解析出來的結果,?結果是?8589934593,?那么它是怎么得到的呢?
#?如果將?packet_data?按照?8?字節(jié)整數(shù)解析,相當于將兩個?4?字節(jié)整數(shù)合并成一個?8?字節(jié)整數(shù)
#?其中整數(shù)?2?占據前?32?個位,整數(shù)?1?占據后?32?個位
print((2?<<?32)?+?1)
"""
8589934593
"""
#?怎么樣,?結果是不是一樣呢??至于這里為什么不是?(1?<<?31)?+?2,?我們后面會說

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

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ù)據進行打包的話,那么使用 struct.Struct 是更推薦的,可以類比正則。

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

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

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

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

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

字節(jié)序

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

那么 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ù)據是一個字節(jié)串,或者理解為?C?的字符數(shù)組
#?而數(shù)組的元素,從左往右對應的內存地址是依次增大的
#?所以結果就是低位存在了低地址中,所以這里是小端存儲
#?而如果想要變成大端存儲的話,?可以這么做
packed_data?=?struct.pack(">i",?1)
print(packed_data)??#?b'\x00\x00\x00\x01'
#?我們看到此時結果就變了

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

import?struct
#?因為?\x01?在最后面,而后面表示內存的高地址
#?此時這個數(shù)字如果想表示?1,?那么它一定是大端存儲的?1
#?也就是將低位的?\x01?放在了高地址中
packed_data?=?b'\x00\x00\x00\x01'
#?這里我們不指定大小端,?默認是小端
print(struct.unpack("i",?packed_data))
"""
(16777216,)
"""
#?我們看到結果變了,?至于這個結果怎么來的,?很簡單
#?無論打包還是解包,如果不指定字節(jié)序,那么默認都是小端,低位存在低地址中
#?所以?b'\x00\x00\x00\x01'?等價于如下
print(0b00000001_00000000_00000000_00000000)?
"""
16777216
"""
#?所以我們也要用大端存儲進行解析,?表示:?我是大端存儲,?存儲在高地址的是數(shù)據的低位
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?轉成一個?long?long,默認是小端,所以低位存在低地址中
#?因此?\x01\x00\x00\x00?表示?1,占據低?32?個位
#?\x02\x00\x00\x00?表示?2,占據高?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)
  • ! : 網絡字節(jié)序(實際上就是大端字節(jié)序)
  • = : 本地字節(jié)序(當前系統(tǒng)的字節(jié)序)
  • @ : 本地字節(jié)序(和 = 相同,但可能有不同的對齊方式)

緩沖區(qū)

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

import?struct
import?ctypes
#?創(chuàng)建一個?string?緩存,?大小為?10
buf?=?ctypes.create_string_buffer(10)
#?raw?表示原始數(shù)據,這里都是?\0,因為?C?中是通過?\0?來標識一個字符串的結束
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ù)據
struct.pack_into("ii2s",?buf,?0,?123,?345,?b"ab")
#?打包之后的數(shù)據會存在?buf?中,解包的話,使用 unpack_from
#?會從?buf?中讀取數(shù)據并解析,第三個參數(shù)表示從偏移量為 0 的位置開始解析
values?=?struct.unpack_from("ii2s",?buf,?0)
print(values)??#?(123,?345,?b'ab')

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

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ù)和二進制流之間的通用轉換邏輯。

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

相關文章

  • python中resample函數(shù)實現(xiàn)重采樣和降采樣代碼

    python中resample函數(shù)實現(xiàn)重采樣和降采樣代碼

    今天小編就為大家分享一篇python中resample函數(shù)實現(xiàn)重采樣和降采樣代碼,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-02-02
  • 詳解Python腳本如何設置試用期

    詳解Python腳本如何設置試用期

    程序員可能會私下給別人開發(fā)一些工具,但是通常要給別人試用一下,但是萬一別人試用后,把你拉黑,那就白忙活了。今天就分享如何在 Python 里設置有效期
    2022-07-07
  • python實現(xiàn)備份目錄的方法

    python實現(xiàn)備份目錄的方法

    這篇文章主要介紹了python實現(xiàn)備份目錄的方法,實例總結了Python實現(xiàn)備份目錄的三種常用技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-08-08
  • python之MSE、MAE、RMSE的使用

    python之MSE、MAE、RMSE的使用

    今天小編就為大家分享一篇python之MSE、MAE、RMSE的使用,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-02-02
  • 基于Python 的語音重采樣函數(shù)解析

    基于Python 的語音重采樣函數(shù)解析

    這篇文章主要介紹了基于Python 的語音重采樣函數(shù)解析,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-07-07
  • python項目打包成docker鏡像并運行的完整指南

    python項目打包成docker鏡像并運行的完整指南

    這篇文章主要為大家詳細介紹了python如何將項目打包成docker鏡像并運行,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下
    2025-09-09
  • Python 含參構造函數(shù)實例詳解

    Python 含參構造函數(shù)實例詳解

    這篇文章主要介紹了Python 含參構造函數(shù)實例詳解的相關資料,需要的朋友可以參考下
    2017-05-05
  • Python Counting Bloom Filter原理與實現(xiàn)詳細介紹

    Python Counting Bloom Filter原理與實現(xiàn)詳細介紹

    這篇文章主要介紹了Python Counting Bloom Filter原理與實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧
    2022-10-10
  • Pytorch 搭建分類回歸神經網絡并用GPU進行加速的例子

    Pytorch 搭建分類回歸神經網絡并用GPU進行加速的例子

    今天小編就為大家分享一篇Pytorch 搭建分類回歸神經網絡并用GPU進行加速的例子,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-01-01
  • Python tcp傳輸代碼實例解析

    Python tcp傳輸代碼實例解析

    這篇文章主要介紹了Python tcp傳輸代碼實例解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-03-03

最新評論

新和县| 犍为县| 海原县| 重庆市| 邮箱| 大城县| 济阳县| 龙川县| 鲁山县| 吉水县| 名山县| 富顺县| 新密市| 财经| 陆丰市| 大新县| 乐清市| 奈曼旗| 衡阳县| 云和县| 读书| 龙井市| 毕节市| 和田市| 平山县| 长乐市| 宾川县| 花垣县| 吐鲁番市| 宁国市| 社旗县| 高要市| 平泉县| 鄂伦春自治旗| 房山区| 五寨县| 乐山市| 滦南县| 盐山县| 洛南县| 三明市|