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
,你得配合位運算符這么寫:
-
-
-
-
-
-
-
-
b'\x00\x9c@c'
非常麻煩。如果換成浮點數就無能為力了。
好在Python提供了一個struct
模塊來解決bytes
和其他二進制數據類型的轉換。
struct
的pack
函數把任意數據類型變成bytes
:
-
-
-
b'\x00\x9c@c'
pack
的第一個參數是處理指令,'>I'
的意思是:
>
表示字節順序是big-endian,也就是網絡序,I
表示4字節無符號整數。
后面的參數個數要和處理指令一致。
struct
准確地講,Python沒有專門處理字節的數據類型。但由於b'str'
可以表示字節,所以,字節數組=二進制str。而在C語言中,我們可以很方便地用struct、union來處理字節,以及字節和int,float的轉換。
在Python中,比方說要把一個32位無符號整數變成字節,也就是4個長度的bytes
,你得配合位運算符這么寫:
-
-
-
-
-
-
-
-
b'\x00\x9c@c'
非常麻煩。如果換成浮點數就無能為力了。
好在Python提供了一個struct
模塊來解決bytes
和其他二進制數據類型的轉換。
struct
的pack
函數把任意數據類型變成bytes
:
-
-
-
b'\x00\x9c@c'
pack
的第一個參數是處理指令,'>I'
的意思是:
>
表示字節順序是big-endian,也就是網絡序,I
表示4字節無符號整數。
后面的參數個數要和處理指令一致。
unpack
把bytes
變成相應的數據類型:
-
-
( 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
讀取:
-
-
( 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.通常的打包和解包
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]