1. 程式人生 > >Python模組學習系列(2)----struct

Python模組學習系列(2)----struct

轉自:http://www.cnblogs.com/coser/archive/2011/12/17/2291160.html

瞭解c語言的人,一定會知道struct結構體在c語言中的作用,它定義了一種結構,裡面包含不同型別的資料(int,char,bool等等),方便對某一結構物件進行處理。而在網路通訊當中,大多傳遞的資料是以二進位制流(binary data)存在的。當傳遞字串時,不必擔心太多的問題,而當傳遞諸如int、char之類的基本資料的時候,就需要有一種機制將某些特定的結構體型別打包成二進位制流的字串然後再網路傳輸,而接收端也應該可以通過某種機制進行解包還原出原始的結構體資料。python中的struct模組就提供了這樣的機制,該模組的主要作用就是對python基本型別值與用python字串格式表示的C struct型別間的轉化(This module performs conversions between Python values and C structs represented as Python strings.)。stuct模組提供了很簡單的幾個函式,下面寫幾個例子。

1、基本的pack和unpack

    struct提供用format specifier方式對資料進行打包和解包(Packing and Unpacking)。例如:

<span style="font-size:18px;">import struct
import binascii
values = (1, 'abc', 2.7)
s = struct.Struct('I3sf')
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</span>

輸出:

Original values: (1, 'abc', 2.7)
Format string : I3sf
Uses : 12 bytes
Packed Value : 0100000061626300cdcc2c40
Unpacked Type : <type 'tuple'>  Value: (1, 'abc', 2.700000047683716)

程式碼中,首先定義了一個元組資料,包含int、string、float三種資料型別,然後定義了struct物件,並制定了format‘I3sf’,I 表示int,3s表示三個字元長度的字串,f 表示 float。最後通過struct的pack和unpack進行打包和解包。通過輸出結果可以發現,value被pack之後,轉化為了一段二進位制位元組串,而unpack可以把該位元組串再轉換回一個元組,但是值得注意的是對於float的精度發生了改變,這是由一些比如作業系統等客觀因素所決定的。打包之後的資料所佔用的位元組數與C語言中的struct十分相似。定義format可以參照官方api提供的對照表:

image

2、位元組順序

   另一方面,打包的後的位元組順序預設上是由作業系統的決定的,當然struct模組也提供了自定義位元組順序的功能,可以指定大端儲存、小端儲存等特定的位元組順序,對於底層通訊的位元組順序是十分重要的,不同的位元組順序和儲存方式也會導致位元組大小的不同。在format字串前面加上特定的符號即可以表示不同的位元組順序儲存方式,例如採用小端儲存 s = struct.Struct(‘<I3sf’)就可以了。官方api library 也提供了相應的對照列表:

image

3、利用buffer,使用pack_into和unpack_from方法

  使用二進位制打包資料的場景大部分都是對效能要求比較高的使用環境。而在上面提到的pack方法都是對輸入資料進行操作後重新建立了一個記憶體空間用於返回,也就是說我們每次pack都會在記憶體中分配出相應的記憶體資源,這有時是一種很大的效能浪費。struct模組還提供了pack_into() 和 unpack_from()的方法用來解決這樣的問題,也就是對一個已經提前分配好的buffer進行位元組的填充,而不會每次都產生一個新物件對位元組進行儲存。

<span style="font-size:18px;">import struct
import binascii
import ctypes
 
values = (1, 'abc', 2.7)
s = struct.Struct('I3sf')
prebuffer = ctypes.create_string_buffer(s.size)
print 'Before :',binascii.hexlify(prebuffer)
s.pack_into(prebuffer,0,*values)
print 'After pack:',binascii.hexlify(prebuffer)
unpacked = s.unpack_from(prebuffer,0)
print 'After unpack:',unpacked</span>

輸出:

Before : 000000000000000000000000
After pack: 0100000061626300cdcc2c40
After unpack: (1, 'abc', 2.700000047683716)
對比使用pack方法打包,pack_into 方法一直是在對prebuffer物件進行操作,沒有產生多餘的記憶體浪費。另外需要注意的一點是,pack_into和unpack_from方法均是對string buffer物件進行操作,並提供了offset引數,使用者可以通過指定相應的offset,使相應的處理變得更加靈活。例如,我們可以把多個物件pack到一個buffer裡面,然後通過指定不同的offset進行unpack:

<span style="font-size:18px;">import struct
import binascii
import ctypes
 
values1 = (1, 'abc', 2.7)
values2 = ('defg',101)
s1 = struct.Struct('I3sf')
s2 = struct.Struct('4sI')
 
prebuffer = ctypes.create_string_buffer(s1.size+s2.size)
print 'Before :',binascii.hexlify(prebuffer)
s1.pack_into(prebuffer,0,*values1)
s2.pack_into(prebuffer,s1.size,*values2)
print 'After pack:',binascii.hexlify(prebuffer)
print s1.unpack_from(prebuffer,0)
print s2.unpack_from(prebuffer,s1.size)</span>

輸出:

Before : 0000000000000000000000000000000000000000
After pack: 0100000061626300cdcc2c406465666765000000
(1, 'abc', 2.700000047683716)
('defg', 101)

補充:1、對於binascii和ctypes模組,後面會講到,這裡不再講述

2、關於大端儲存和小端儲存,記得這還是上嵌入式時講到的知識,這裡簡單介紹一下:

    大端模式,是指資料的高位元組儲存在記憶體的低地址中,而資料的低位元組儲存在記憶體的高地址中,這樣的儲存模式有點兒類似於把資料當作字串順序處理:地址由小向大增加,而資料從高位往低位放;     小端模式,是指資料的高位元組儲存在記憶體的高地址中,而資料的低位元組儲存在記憶體的低地址中,這種儲存模式將地址的高低和資料位權有效地結合起來,高地址部分權值高,低地址部分權值低,和我們的邏輯方法一致。

更詳細的可以參考這篇部落格:http://blog.csdn.net/hackbuteer1/article/details/7722667