python之struct詳解


python之struct詳解

版權聲明:本文為博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
本文鏈接: https://blog.csdn.net/qq_30638831/article/details/80421019

 

用處

  1. 按照指定格式將Python數據轉換為字符串,該字符串為字節流,如網絡傳輸時,不能傳輸int,此時先將int轉化為字節流,然后再發送;
  2. 按照指定格式將字節流轉換為Python指定的數據類型;
  3. 處理二進制數據,如果用struct來處理文件的話,需要用’wb’,’rb’以二進制(字節流)寫,讀的方式來處理文件;
  4. 處理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  

注- -!

  1. _Bool在C99中定義,如果沒有這個類型,則將這個類型視為char,一個字節;
  2. q和Q只適用於64位機器;
  3. 每個格式前可以有一個數字,表示這個類型的個數,如s格式表示一定長度的字符串,4s表示長度為4的字符串;4i表示四個int;
  4. P用來轉換一個指針,其長度和計算機相關;
  5. 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,你得配合位運算符這么寫:

 

  1.  
    >>> n = 10240099
  2.  
    >>> b1 = (n & 0xff000000) >> 24
  3.  
    >>> b2 = (n & 0xff0000) >> 16
  4.  
    >>> b3 = (n & 0xff00) >> 8
  5.  
    >>> b4 = n & 0xff
  6.  
    >>> bs = bytes([b1, b2, b3, b4])
  7.  
    >>> bs
  8.  
    b'\x00\x9c@c'

非常麻煩。如果換成浮點數就無能為力了。

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

structpack函數把任意數據類型變成bytes

  1.  
    >>> import struct
  2.  
    >>> struct.pack('>I', 10240099)
  3.  
    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,你得配合位運算符這么寫:

  1.  
    >>> n = 10240099
  2.  
    >>> b1 = (n & 0xff000000) >> 24
  3.  
    >>> b2 = (n & 0xff0000) >> 16
  4.  
    >>> b3 = (n & 0xff00) >> 8
  5.  
    >>> b4 = n & 0xff
  6.  
    >>> bs = bytes([b1, b2, b3, b4])
  7.  
    >>> bs
  8.  
    b'\x00\x9c@c'

非常麻煩。如果換成浮點數就無能為力了。

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

structpack函數把任意數據類型變成bytes

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

pack的第一個參數是處理指令,'>I'的意思是:

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

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

unpackbytes變成相應的數據類型:

  1.  
    >>> struct.unpack('>IH', b'\xf0\xf0\xf0\xf0\x80\x80')
  2.  
    ( 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讀取:

  1.  
    >>> struct.unpack('<ccIIIIIIHH', s)
  2.  
    ( b'B', b'M', 691256, 0, 54, 40, 640, 360, 1, 24)
  3.  
     

結果顯示,b'B'b'M'說明是Windows位圖,位圖大小為640x360,顏色數為24。

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

  1.  
    # -*- coding: utf-8 -*-
  2.  
     
  3.  
    import base64,struct
  4.  
     
  5.  
    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==')
  6.  
     
  7.  
     
  8.  
    def bmp_info(data):
  9.  
     
  10.  
    str = struct.unpack( '<ccIIIIIIHH',data[:30]) #bytes類也有切片方法
  11.  
     
  12.  
     
  13.  
    if str[0]==b'B' and str[1]==b'M':
  14.  
     
  15.  
    print( "這是位圖文件")
  16.  
     
  17.  
    return {
  18.  
    'width': str[-4],
  19.  
    'height': str[-3],
  20.  
    'color': str[-1]
  21.  
    }
  22.  
     
  23.  
    else:
  24.  
     
  25.  
    print( "這不是位圖文件")
  26.  
     
  27.  
     
  28.  
    if __name__ == '__main__':
  29.  
    bmp_info(bmp_data)
  30.  
    print( 'ok')
  31.  
     
  32.  
     

 

 

>>> 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.通常的打包和解包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# -*- 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)

返回字節流的十六進制字節流。

 

1
2
3
4
5
6
7
>>> a =  'hello'
>>> b = a.encode()
>>> b
b 'hello'
>>> c = binascii.hexlify(b)
>>> c
b '68656c6c6f'

 

 

 

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# -*- 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方式來打包多個對象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# -*- 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]

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM