Python—— Struct

用處
按照指定格式將Python數據轉換爲字符串,該字符串爲字節流,如網絡傳輸時,不能傳輸int,此時先將int轉化爲字節流,然後再發送;
按照指定格式將字節流轉換爲Python指定的數據類型;
處理二進制數據,如果用struct來處理文件的話,需要用’wb’,’rb’以二進制(字節流)寫,讀的方式來處理文件;
處理c語言中的結構體;
struct模塊中的函數
函數    return    explain
pack(fmt,v1,v2…)    string    按照給定的格式(fmt),把數據轉換成字符串(字節流),並將該字符串返回.
pack_into(fmt,buffer,offset,v1,v2…)    None    按照給定的格式(fmt),將數據轉換成字符串(字節流),並將字節流寫入以offset開始的buffer中.(buffer爲可寫的緩衝區,可用array模塊)
unpack(fmt,v1,v2…..)    tuple    按照給定的格式(fmt)解析字節流,並返回解析結果
pack_from(fmt,buffer,offset)    tuple    按照給定的格式(fmt)解析以offset開始的緩衝區,並返回解析結果
calcsize(fmt)    size of fmt    計算給定的格式(fmt)佔用多少字節的內存,注意對齊方式
格式化字符串
當打包或者解包的時,需要按照特定的方式來打包或者解包.該方式就是格式化字符串,它指定了數據類型,除此之外,還有用於控制字節順序、大小和對齊方式的特殊字符.

對齊方式
爲了同c中的結構體交換數據,還要考慮c或c++編譯器使用了字節對齊,通常是以4個字節爲單位的32位系統,故而struct根據本地機器字節順序轉換.可以用格式中的第一個字符來改變對齊方式.定義如下

Character    Byte order    Size    Alignment
@(默認)    本機    本機    本機,湊夠4字節
=    本機    標準    none,按原字節數
<    小端    標準    none,按原字節數
>    大端    標準    none,按原字節數
!    network(大端)    標準    none,按原字節數
如果不懂大小端,見大小端參考網址.

格式符
格式符    C語言類型    Python類型    Standard size
x    pad byte(填充字節)    no value     
c    char    string of length 1    1
b    signed char    integer    1
B    unsigned char    integer    1
?    _Bool    bool    1
h    short    integer    2
H    unsigned short    integer    2
i    int    integer    4
I(大寫的i)    unsigned int    integer    4
l(小寫的L)    long    integer    4
L    unsigned long    long    4
q    long long    long    8
Q    unsigned long long    long    8
f    float    float    4
d    double    float    8
s    char[]    string     
p    char[]    string     
P    void *    long     
注- -!

_Bool在C99中定義,如果沒有這個類型,則將這個類型視爲char,一個字節;
q和Q只適用於64位機器;
每個格式前可以有一個數字,表示這個類型的個數,如s格式表示一定長度的字符串,4s表示長度爲4的字符串;4i表示四個int;
P用來轉換一個指針,其長度和計算機相關;
f和d的長度和計算機相關;
進制轉化:

# 獲取用戶輸入十進制數
dec = int(input("輸入數字:"))

print("十進制數爲:", dec)
print("轉換爲二進制爲:", bin(dec))
print("轉換爲八進制爲:", oct(dec))
print("轉換爲十六進制爲:", hex(dec))
16進制轉10進制: int('0x10', 16)  ==>  16
Python沒有專門處理字節的數據類型。但由於b'str'可以表示字節,所以,字節數組=二進制str。而在C語言中,我們可以很方便地用struct、union來處理字節,以及字節和int,float的轉換。

在Python中,比方說要把一個32位無符號整數變成字節,也就是4個長度的bytes,你得配合位運算符這麼寫:

>>> n = 10240099
>>> b1 = (n & 0xff000000) >> 24
>>> b2 = (n & 0xff0000) >> 16
>>> b3 = (n & 0xff00) >> 8
>>> b4 = n & 0xff
>>> bs = bytes([b1, b2, b3, b4])
>>> bs
b'\x00\x9c@c'
非常麻煩。如果換成浮點數就無能爲力了。

好在Python提供了一個struct模塊來解決bytes和其他二進制數據類型的轉換。

struct的pack函數把任意數據類型變成bytes:

>>> import struct
>>> struct.pack('>I', 10240099)
b'\x00\x9c@c'
pack的第一個參數是處理指令,'>I'的意思是:

>表示字節順序是big-endian,也就是網絡序,I表示4字節無符號整數。

後面的參數個數要和處理指令一致。

struct
閱讀: 58181
準確地講,Python沒有專門處理字節的數據類型。但由於b'str'可以表示字節,所以,字節數組=二進制str。而在C語言中,我們可以很方便地用struct、union來處理字節,以及字節和int,float的轉換。

在Python中,比方說要把一個32位無符號整數變成字節,也就是4個長度的bytes,你得配合位運算符這麼寫:

>>> n = 10240099
>>> b1 = (n & 0xff000000) >> 24
>>> b2 = (n & 0xff0000) >> 16
>>> b3 = (n & 0xff00) >> 8
>>> b4 = n & 0xff
>>> bs = bytes([b1, b2, b3, b4])
>>> bs
b'\x00\x9c@c'
非常麻煩。如果換成浮點數就無能爲力了。

好在Python提供了一個struct模塊來解決bytes和其他二進制數據類型的轉換。

struct的pack函數把任意數據類型變成bytes:

>>> import struct
>>> struct.pack('>I', 10240099)
b'\x00\x9c@c'
pack的第一個參數是處理指令,'>I'的意思是:

>表示字節順序是big-endian,也就是網絡序,I表示4字節無符號整數。

後面的參數個數要和處理指令一致。

unpack把bytes變成相應的數據類型:

>>> struct.unpack('>IH', b'\xf0\xf0\xf0\xf0\x80\x80')
(4042322160, 32896)
根據>IH的說明,後面的bytes依次變爲I:4字節無符號整數和H:2字節無符號整數。

所以,儘管Python不適合編寫底層操作字節流的代碼,但在對性能要求不高的地方,利用struct就方便多了。

struct模塊定義的數據類型可以參考Python官方文檔:

https://docs.python.org/3/library/struct.html#format-characters

Windows的位圖文件(.bmp)是一種非常簡單的文件格式,我們來用struct分析一下。

首先找一個bmp文件,沒有的話用“畫圖”畫一個。

讀入前30個字節來分析:

>>> s = b'\x42\x4d\x38\x8c\x0a\x00\x00\x00\x00\x00\x36\x00\x00\x00\x28\x00\x00\x00\x80\x02\x00\x00\x68\x01\x00\x00\x01\x00\x18\x00'
BMP格式採用小端方式存儲數據,文件頭的結構按順序如下:

兩個字節:'BM'表示Windows位圖,'BA'表示OS/2位圖;一個4字節整數:表示位圖大小;一個4字節整數:保留位,始終爲0;一個4字節整數:實際圖像的偏移量;一個4字節整數:Header的字節數;一個4字節整數:圖像寬度;一個4字節整數:圖像高度;一個2字節整數:始終爲1;一個2字節整數:顏色數。

所以,組合起來用unpack讀取:

>>> struct.unpack('<ccIIIIIIHH', s)
(b'B', b'M', 691256, 0, 54, 40, 640, 360, 1, 24)
結果顯示,b'B'、b'M'說明是Windows位圖,位圖大小爲640x360,顏色數爲24。

請編寫一個bmpinfo.py,可以檢查任意文件是否是位圖文件,如果是,打印出圖片大小和顏色數。

# -*- coding: utf-8 -*-
 
import base64,struct
 
bmp_data = base64.b64decode('Qk1oAgAAAAAAADYAAAAoAAAAHAAAAAoAAAABABAAAAAAADICAAASCwAAEgsAAAAAAAAAAAAA/3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9/AHwAfAB8AHwAfAB8AHwAfP9//3//fwB8AHwAfAB8/3//f/9/AHwAfAB8AHz/f/9//3//f/9//38AfAB8AHwAfAB8AHwAfAB8AHz/f/9//38AfAB8/3//f/9//3//fwB8AHz/f/9//3//f/9//3//f/9/AHwAfP9//3//f/9/AHwAfP9//3//fwB8AHz/f/9//3//f/9/AHwAfP9//3//f/9//3//f/9//38AfAB8AHwAfAB8AHwAfP9//3//f/9/AHwAfP9//3//f/9//38AfAB8/3//f/9//3//f/9//3//fwB8AHwAfAB8AHwAfAB8/3//f/9//38AfAB8/3//f/9//3//fwB8AHz/f/9//3//f/9//3//f/9/AHwAfP9//3//f/9/AHwAfP9//3//fwB8AHz/f/9/AHz/f/9/AHwAfP9//38AfP9//3//f/9/AHwAfAB8AHwAfAB8AHwAfAB8/3//f/9/AHwAfP9//38AfAB8AHwAfAB8AHwAfAB8/3//f/9//38AfAB8AHwAfAB8AHwAfAB8/3//f/9/AHwAfAB8AHz/fwB8AHwAfAB8AHwAfAB8AHz/f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//38AAA==')
 
 
def bmp_info(data):
 
    str = struct.unpack('<ccIIIIIIHH',data[:30]) #bytes類也有切片方法
 
 
    if str[0]==b'B' and str[1]==b'M':
 
        print("這是位圖文件")
 
        return {
            'width': str[-4],
            'height': str[-3],
            'color': str[-1]
            }
 
    else:
 
        print("這不是位圖文件")
 
 
if __name__ == '__main__':
    bmp_info(bmp_data)
    print('ok')
 


>>> from struct import *
>>> pack('hhl', 1, 2, 3)
b'\x00\x01\x00\x02\x00\x00\x00\x03'
>>> unpack('hhl', b'\x00\x01\x00\x02\x00\x00\x00\x03')
(1, 2, 3)
>>> calcsize('hhl')
8
Unpacked fields can be named by assigning them to variables or by wrapping the result in a named tuple:

>>>
>>> record = b'raymond   \x32\x12\x08\x01\x08'
>>> name, serialnum, school, gradelevel = unpack('<10sHHb', record)

>>> from collections import namedtuple
>>> Student = namedtuple('Student', 'name serialnum school gradelevel')
>>> Student._make(unpack('<10sHHb', record))
Student(name=b'raymond   ', serialnum=4658, school=264, gradelevel=8)
The ordering of format characters may have an impact on size since the padding needed to satisfy alignment requirements is different:

>>>
>>> pack('ci', b'*', 0x12131415)
b'*\x00\x00\x00\x12\x13\x14\x15'
>>> pack('ic', 0x12131415, b'*')
b'\x12\x13\x14\x15*'
>>> calcsize('ci')
8
>>> calcsize('ic')
5
The following format 'llh0l' specifies two pad bytes at the end, assuming longs are aligned on 4-byte boundaries:

>>>
>>> pack('llh0l', 1, 2, 3)
b'\x00\x00\x00\x01\x00\x00\x00\x02\x00\x03\x00\x00'

示例

     現在我們有了格式字符串,也知道了封裝函數,那現在先通過一兩個例子看一看。

      例一:比如有一個報文頭部在C語言中是這樣定義的

      struct header

      {

          unsigned short  usType;

          char[4]               acTag;

          unsigned int      uiVersion;

          unsigned int      uiLength;

      };

      在C語言對將該結構體封裝到一塊緩存中是很簡單的,可以使用memcpy()實現。在Python中,使用struct就需要這樣:

              str = struct.pack('B4sII', 0x04, 'aaaa', 0x01, 0x0e)

      'B4sII'  ------   有一個unsigned short、char[4], 2個unsigned int。其中s之前的數字說明了字符串的大小 。

              type, tag, version, length = struct.unpack('B4sll', str)

class struct.Struct(format)
返回一個struct對象(結構體,參考C)。

該對象可以根據格式化字符串的格式來讀寫二進制數據。

第一個參數(格式化字符串)可以指定字節的順序。

默認是根據系統來確定,也提供自定義的方式,只需要在前面加上特定字符即可:

struct.Struct('>I4sf')
特定字符對照表附件有。

常見方法和屬性:

方法

pack
(v1, v2, …)
返回一個字節流對象。

按照fmt(格式化字符串)的格式來打包參數v1,v2,...。

通俗的說就是:

首先將不同類型的數據對象放在一個“組”中(比如元組(1,'good',1.22)),

然後打包(“組”轉換爲字節流對象),最後再解包(將字節流對象轉換爲“組”)。

pack_into(buffer, offset, v1, v2, …)
根據格式字符串fmt包裝值v1,v2,...,並將打包的字節寫入從位置偏移開始的可寫緩衝buffer。 請注意,offset是必需的參數。

unpack_from(buffer, offset=0)
根據格式字符串fmt,從位置偏移開始從緩衝區解包。 結果是一個元組,即使它只包含一個項目。 緩衝區的大小(以字節爲單位,減去偏移量)必須至少爲格式所需的大小,如calcsize()所反映的。

屬性

format

格式化字符串。

size

結構體的大小。

 

實例:

1.通常的打包和解包

# -*- coding: utf-8 -*-
"""
打包和解包
"""
import struct
import binascii
 
values = (1, b'good', 1.22) #查看格式化對照表可知,字符串必須爲字節流類型。
s = struct.Struct('I4sf')
packed_data = s.pack(*values)
unpacked_data = s.unpack(packed_data)
  
print('Original values:', values)
print('Format string :', s.format)
print('Uses :', s.size, 'bytes')
print('Packed Value :', binascii.hexlify(packed_data))
print('Unpacked Type :', type(unpacked_data), ' Value:', unpacked_data)


 結果:

Original values: (1, b'good', 1.22)
Format string : b'I4sf'
Uses : 12 bytes
Packed Value : b'01000000676f6f64f6289c3f'
Unpacked Type : <class 'tuple'>  Value: (1, b'good', 1.2200000286102295)
[Finished in 0.1s]
說明:

首先將數據對象放在了一個元組中,然後創建一個Struct對象,並使用pack()方法打包該元組;最後解包返回該元組。

這裏使用到了binascii.hexlify(data)函數。

binascii.hexlify(data)
返回字節流的十六進制字節流。
>>> a = 'hello'
>>> b = a.encode()
>>> b
b'hello'
>>> c = binascii.hexlify(b)
>>> c
b'68656c6c6f'
 

2.使用buffer來進行打包和解包

 使用通常的方式來打包和解包會造成內存的浪費,所以python提供了buffer的方式:

# -*- coding: utf-8 -*-
"""
通過buffer方式打包和解包
"""
import struct
import binascii
import ctypes
 
values = (1, b'good', 1.22) #查看格式化字符串可知,字符串必須爲字節流類型。
s = struct.Struct('I4sf')
buff = ctypes.create_string_buffer(s.size)
packed_data = s.pack_into(buff,0,*values)
unpacked_data = s.unpack_from(buff,0)
  
print('Original values:', values)
print('Format string :', s.format)
print('buff :', buff)
print('Packed Value :', binascii.hexlify(buff))
print('Unpacked Type :', type(unpacked_data), ' Value:', unpacked_data)


 結果:


Original values1: (1, b'good', 1.22)
Original values2: (b'hello', True)
buff : <ctypes.c_char_Array_18 object at 0x000000D5A5617348>
Packed Value : b'01000000676f6f64f6289c3f68656c6c6f01'
Unpacked Type : <class 'tuple'>  Value: (1, b'good', 1.2200000286102295)
Unpacked Type : <class 'tuple'>  Value: (b'hello', True)
[Finished in 0.1s]

說明:

針對buff對象進行打包和解包,避免了內存的浪費。

這裏使用到了函數

ctypes.create_string_buffer(init_or_size,size = None)
創建可變字符緩衝區。
返回的對象是c_char的ctypes數組。

init_or_size必須是一個整數,它指定數組的大小,或者用於初始化數組項的字節對象。

3.使用buffer方式來打包多個對象

# -*- coding: utf-8 -*-
"""
buffer方式打包和解包多個對象
"""
import struct
import binascii
import ctypes
 
values1 = (1, b'good', 1.22) #查看格式化字符串可知,字符串必須爲字節流類型。
values2 = (b'hello',True)
s1 = struct.Struct('I4sf')
s2 = struct.Struct('5s?')
buff = ctypes.create_string_buffer(s1.size+s2.size)
packed_data_s1 = s1.pack_into(buff,0,*values1)
packed_data_s2 = s2.pack_into(buff,s1.size,*values2)
unpacked_data_s1 = s1.unpack_from(buff,0)
unpacked_data_s2 = s2.unpack_from(buff,s1.size)
  
print('Original values1:', values1)
print('Original values2:', values2)
print('buff :', buff)
print('Packed Value :', binascii.hexlify(buff))
print('Unpacked Type :', type(unpacked_data_s1), ' Value:', unpacked_data_s1)
print('Unpacked Type :', type(unpacked_data_s2), ' Value:', unpacked_data_s2)


 結果:

Original values2: (b'hello', True)
buff : <ctypes.c_char_Array_18 object at 0x000000D5A5617348>
Packed Value : b'01000000676f6f64f6289c3f68656c6c6f01'
Unpacked Type : <class 'tuple'>  Value: (1, b'good', 1.2200000286102295)
Unpacked Type : <class 'tuple'>  Value: (b'hello', True)
[Finished in 0.1s]
————————————————
版權聲明:本文爲CSDN博主「醉小義」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/qq_30638831/article/details/80421019

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章