python struct类的使用

模块说明(参考官方手册):

此模块执行Python值和表示为Python 对象的C 结构之间的转换bytes。这可用于处理存储在文件中的二进制数据或来自网络连接以及其他来源。它使用 Format Strings作为C结构布局的简洁描述以及与Python值的预期转换。

注意 默认情况下,打包给定C 结构的结果包括填充字节,以便维护所涉及的C类型的正确对齐; 类似地,在拆包时考虑对齐。选择此行为是为了使压缩结构的字节与相应C 结构的内存中的布局完全对应。要处理与平台无关的数据格式或省略隐式填充字节,请使用standard大小和对齐而不是 native大小和对齐:有关详细信息,请参阅字节顺序,大小和对齐。

(1)用途

1、按照指定格式将Python数据转换为字符串,该字符串为字节流
如:网络传输时,不能传输int,此时先将int转化为字节流,然后再发送;
2、按照指定格式将字节流转换为Python指定的数据类型;
3、处理二进制数据,如果用struct来处理图片文件的话,需要使用‘rb’/‘wb’以二进制(字节流)读写的方式来处理文件;
4、处理c语言中的结构体;

(2)格式化字符串

对齐方式:

默认情况下,C类型以机器的本机格式和字节顺序表示,并在必要时通过跳过填充字节进行正确对齐(根据C编译器使用的规则)。或者,格式字符串的第一个字符可用于指示打包数据的字节顺序,大小和对齐方式,如下表所示:
python struct类的使用
关于本机字节顺序:
是big-endian(大端)或little-endian(小端),具体取决于主机系统。例如,Intel x86和AMD64(x86-64)是little-endian; 摩托罗拉68000和PowerPC G5都是大端的; ARM和Intel Itanium具有可切换的字节序(双端)。
使用sys.byteorder来检查你的系统的字节序。

格式字符:

根据类型,C和Python值之间的转换应该是显而易见的。“标准大小”列是指使用标准大小时打包值的大小(以字节为单位); 也就是说,当格式字符串中的一个开始’<’,’>’,’!'或 ‘=’。使用本机大小时,打包值的大小取决于平台。
python struct类的使用

(3)关于网络字节序大小端以及相关概念

名称 说明 口诀
大端 高地址存储低位数据 高低低高
小端 高地址存储高位数据 高高低低
高地址 数值大的地址,如:0x01
低地址 0x00
高位 权最高(最左端数据)
低位 最右端数据

注:数组首地址为低地址,这个在判断大小端时用到

(4)网络字节流的通讯(socket通讯)

1、譬如要通过串口发送一个0x12345678给接收方,但是因为串口本身限制,只能以字节为单位来发送,所以需要发4次;接收方分4次接收,内容分别是:0x12、0x34、0x56、0x78.接收方接收到这4个字节之后需要去重组得到0x12345678(而不是得到0x78563412).
2、所以在通信双方需要有一个默契,就是:先发/先接的是高位还是低位?这就是通信中的大小端问题。
3、一般来说是:先发低字节叫小端;先发高字节就叫大端。(我不能确定)实际操作中,在通信协议里面会去定义大小端,明确告诉你先发的是低字节还是高字节。
4、在通信协议中,大小端是非常重要的,大家使用别人定义的通信协议还是自己要去定义通信协议,一定都要注意标明通信协议中大小端的问题。

(5)示例代码

环境说明:python3.7,struct函数用法官方手册

import struct,array
def local_is_big_endian():
    #十六进制:0x12345678转化为int类型是305419896
    #a=305419896
    a=0x12345678#占四字节,int型数据
    result=struct.pack('i',a)#按规定格式打包,返回字符串
    #取到低地址数据判断
    #print(type(result))
    #print(result[0])
    if hex(result[0]) == '0x78':
        print('低地址存放低位数据,本机为小端!!')
    elif hex(result[0]) == '0x12':
        print('低地址存放高位数据,本机为大端!!')

def test_size():
    format_list=['@ci','=ci','<ci','>ci','!ci','@ic','=ic','<ic','>ic','!ic','@c','@i']
    for format_i in format_list:
        print(format_i,struct.calcsize(format_i))

def pack_test(Format='>ii',msg=[0x12345678,0x11223344]):
    #格式:大端两个有符号int 打包对象:十六进制数据(两个,个数要对上)
    #使用列表时:注意带上星号
    result=struct.pack(Format,*msg)
    print(type(result))
    for i in result:
        print(hex(i))
    print('解包:')
    un_result=struct.unpack(Format,result)
    for i in un_result:
        print(hex(i))

def pack_into_test(Format='>ii',msg=[0x12345678,0x11223344]):
    #步骤:1.准备指定大小的缓冲区,2.打包解包

    #创建8字节缓冲区,导入模块array,'b'代表一个字节
    buffer=array.array('b',range(8))
    
    #参数说明:打包格式,缓冲区,偏移地址,打包对象
    #打包到缓冲
    struct.pack_into(Format,buffer,0,*msg)

    print(type(buffer))
    for i in buffer:
        print(hex(i))

    print('解包:')
    #格式,缓冲区,偏移量
    #解包到un_result
    un_result=struct.unpack_from(Format,buffer,0)
    for i in un_result:
        print(hex(i))

if __name__=='__main__':
    print('检查本地的网络字节序:')
    local_is_big_endian()
    print('测试本机上各种fomat大小:')
    test_size()
    
    print('打包与解包实验:')
    print('大端:')
    pack_test()
    print('小端:')
    pack_test('<ii')

    print('基于缓冲区的打包与解包实验:')
    print('大端:')
    pack_into_test()
    print('小端:')
    pack_into_test('<ii')

结果:
python struct类的使用