python struct库

struct 模块主要用于打包和解包二进制数据。

介绍

struct 模块是 Python 的一个标准库,它提供了一种将 Python 值打包为 C 结构体的方式,以及从 C 结构体解包为 Python 值的方式。这对于处理二进制数据特别有用,比如在文件操作、网络通信或低级数据处理时。

import struct

函数

struct.pack(format, v1, v2, )
返回一个 bytes 对象,其中包含根据格式字符串 format 打包的值 v1, v2, … 参数个数必须与格式字符串所要求的值完全匹配。
struct.pack_into(format, buffer, offset, v1, v2, )
根据格式字符串 format 打包 v1, v2, … 等值并将打包的字节串写入可写缓冲区 bufferoffset 开始的位置。 请注意 offset 是必需的参数。
struct.unpack(format, buffer)
根据格式字符串 format 从缓冲区 buffer 解包(假定是由 pack(format, ...) 打包)。 结果为一个元组,即使其只包含一个条目。 缓冲区的字节大小必须匹配格式所要求的大小,如 calcsize() 所示。
struct.unpack_from(format, /, buffer, offset=0)
buffer 从位置 offset 开始根据格式字符串 format 进行解包。 结果为一个元组,即使其中只包含一个条目。 缓冲区的字节大小从位置 offset 开始必须至少为 calcsize() 显示的格式所要求的大小。
struct.iter_unpack(format, buffer)
根据格式字符串 format 以迭代方式从缓冲区 buffer 中解包。 此函数返回一个迭代器,它将从缓冲区读取大小相等的块直到其所有内容耗尽为止。 缓冲区的字节大小必须是格式所要求的大小的整数倍,如 calcsize() 所显示的。每次迭代将产生一个如格式字符串所指定的元组。
struct.calcsize(format)
返回与格式字符串 format 相对应的结构的大小(亦即 pack(format, ...) 所产生的字节串对象的大小)。

异常

exception struct.error 会在多种场合下被引发的异常;其参数为一个描述错误信息的字符串。

import struct

try:
    # 错误的格式字符串,'a' 不是一个有效的格式字符用于打包整数
    packed_data = struct.pack('a', 1)
except struct.error as e:
    print(f"Error: {e}")  # 输出错误信息

格式字符串

格式字符串描述了打包和解包数据时的数据布局。 它们是使用格式字符来构建的,格式字符指明被打包/解包的数据的类型。 此外,还有用来控制 字节顺序、大小和对齐的特殊字符。 每个格式字符串都是由一个可选的描述数据总体属性的前缀字符和一个或多个描述实际数据值和填充的格式字符组成的。

字节顺序,大小和对齐方式

字符 字节顺序 大小 对齐方式
@ 按原字节 按原字节 按原字节
= 按原字节 标准
< 小端 标准
> 大端 标准
! 网络(=大端) 标准
import struct
struct.pack('>h', 1023)
b'\x03\xff'
struct.pack('<h', 1023)
b'\xff\x03'

注释:

  1. 填充只会在连续结构成员之间自动添加。 填充不会添加到已编码结构的开头和末尾。

  2. 当使用非本机大小和对齐方式即 ‘<’, ‘>’, ‘=’, and ‘!’ 时不会添加任何填充。

  3. 要将结构的末尾对齐到符合特定类型的对齐要求,请以该类型代码加重复计数的零作为格式结束。

扩展:

sys.byteorder 是 Python 中的一个属性,它用于指示 Python 解释器所运行的系统的字节顺序(即字节序)。字节序决定了多字节数据(如整数或浮点数)在内存中的存储方式。主要有两种字节序:

  1. ‘little’:小端字节序。在这种字节序中,数据的最低有效字节(Least Significant Byte, LSB)存储在最低的内存地址,而最高有效字节(Most Significant Byte, MSB)存储在最高的内存地址。这是许多现代计算机体系结构的默认字节序。

  2. ‘big’:大端字节序。在这种字节序中,数据的最高有效字节(MSB)存储在最低的内存地址,而最低有效字节(LSB)存储在最高的内存地址。

    import sys
    print(sys.byteorder)
    

sys.byteorder 属性是一个字符串,其值要么是 'little',要么是 'big',取决于 Python 解释器运行的底层系统的字节序。这个属性对于跨平台编程尤其重要,因为不同的硬件平台可能使用不同的字节序,而正确的字节序对于数据的正确解释至关重要。

格式字符

struct 模块支持多种格式字符,用于表示不同的数据类型。以下是一些常用的格式字符:

  • x:填充字节(不占用任何存储空间)
  • c:字符(长度为1的字符串)
  • b:有符号字符(整数)
  • B:无符号字符(整数)
  • ?:布尔值(整数)
  • h:有符号短整型(整数)
  • H:无符号短整型(整数)
  • i:有符号整型(整数)
  • I:无符号整型(整数)
  • l:有符号长整型(整数)
  • L:无符号长整型(整数,注意在32位系统上可能与 I 相同)
  • q:有符号长长整型(整数)
  • Q:无符号长长整型(整数)
  • f:浮点数
  • d:双精度浮点数
  • s:字符串(需要指定长度)
  • p:指向字符串的指针(需要指定长度,实际上存储的是字符串的内存地址,这在 Python 中通常不适用)
  • 格式 C 类型 Python 类型 标准大小 备注
    x 填充字节 (7)
    c char 长度为 1 的字节串 1
    b signed char 整数 1 (1), (2)
    B unsigned char 整数 1 (2)
    ? _Bool bool 1 (1)
    h short 整数 2 (2)
    H unsigned short 整数 2 (2)
    i int 整数 4 (2)
    I unsigned int 整数 4 (2)
    l long 整数 4 (2)
    L unsigned long 整数 4 (2)
    q long long 整数 8 (2)
    Q unsigned long long 整数 8 (2)
    n ssize_t 整数 (3)
    N size_t 整数 (3)
    e (6) float 2 (4)
    f float float 4 (4)
    d double float 8 (4)
    s char[] 字节串 (9)
    p char[] 字节串 (8)
    P void* 整数 (5)

    注意事项:

  • 当使用 s 格式字符时,需要在格式字符串中指定字符串的最大长度,例如 '5s' 表示一个最大长度为5的字符串。
  • 在不同的平台上(尤其是32位和64位系统之间),lL 格式的行为可能有所不同。为了确保跨平台兼容性,推荐使用 iI
  • struct 模块处理的数据是字节对齐的,这意味着某些情况下,为了保持数据的对齐,可能会在结构体中插入填充字节。可以通过在格式字符串前添加特定的字符来控制对齐方式,例如 '@''<''>''=''!' 分别表示原生对齐、小端对齐、大端对齐、网络字节顺序(大端)和对齐为1字节边界(主要用于非对齐的访问)。
  • # 打包和解包三种不同大小的整数,使用大端序:
    from struct import *
    pack(">bhl", 1, 2, 3)
    # b'\x01\x00\x02\x00\x00\x00\x03'
    unpack('>bhl', b'\x01\x00\x02\x00\x00\x00\x03')
    # (1, 2, 3)
    calcsize('>bhl')
    # 7
    

    class struct.Struct(format)
    返回一个新的 Struct 对象,它会根据格式字符串 format 来写入和读取二进制数据。 一次性地创建 Struct 对象并调用其方法相比调用相同格式的模块层级函数效率更高因为格式字符串只会被编译一次。

    已编译的 Struct 对象支持以下方法和属性:

    pack(v1, v2, ) 等价于 pack() 函数,使用了已编译的格式。 (len(result) 将等于 size。)
    pack_into(buffer, offset, v1, v2, ) 等价于 pack_into() 函数,使用了已编译的格式。
    unpack(buffer) 等价于 unpack() 函数,使用了已编译的格式。 缓冲区的字节大小必须等于 size。
    unpack_from(buffer, offset=0) 等价于 unpack_from() 函数,使用了已编译的格式。 缓冲区的字节大小从位置 offset 开始必须至少为 size。
    iter_unpack(buffer) 等价于 iter_unpack() 函数,使用了已编译的格式。 缓冲区的大小必须为 size 的整数倍。
    format 用于构造此 Struct 对象的格式字符串。
    size 计算出对应于 format 的结构大小(亦即 pack() 方法所产生的字节串对象的大小)。
    import struct
    
    # 创建一个 Struct 对象,定义结构体包含一个整数和一个浮点数
    my_struct = struct.Struct('if')
    
    # 打包数据:将 Python 中的数据转换成二进制形式
    packed_data = my_struct.pack(1, 3.14)
    
    # 输出打包后的数据(二进制形式)
    print(packed_data)
    # b'\x01\x00\x00\x00\xc3\xf5H@'
    
    # 解包数据:将二进制数据转换回 Python 中的数据
    unpacked_data = my_struct.unpack(packed_data)
    
    # 输出解包后的数据
    print(unpacked_data, my_struct.size)
    # (1, 3.140000104904175) 8
    

    使用

    打包pack‌:将Python的基本数据类型转换为字节流。

    import struct
    
    int_value = 123456
    float_value = 3.14159
    packed_data = struct.pack('if', int_value, float_value)
    print(f'Packed Data: {packed_data}')
    # Packed Data: b'@\xe2\x01\x00\xd0\x0fI@'
    print(f'Packed Data Length: {len(packed_data)} bytes')
    # Packed Data Length: 8 bytes
    

    解包unpack‌:将字节流转换回Python的基本数据类型。

    unpacked_data = struct.unpack('if', packed_data)
    print(f'Unpacked Data: {unpacked_data}')
    # Unpacked Data: (123456, 3.141590118408203)
    

    计算大小calcsize‌:计算给定格式字符串的结构体的大小。

    print(f'packed_data_length: {struct.calcsize('if')} ')
    # packed_data_length: 8
    

    python字符串数据类型打包:

    import struct
    # str_value = b'tyjiayou'
    str_value = 'tyjiayou'.encode('utf-8')
    # 8s八个字符的长度(len(str_value)s)
    str_struct = struct.pack(f'{len(str_value)}s', str_value)
    print('info:', str_struct, '; len:', len(str_struct))
    # info: b'tyjiayou' ; len: 8
    

    一次打包多个:

    int_value = 123456
    float_value = 3.14159
    bool_value = True
    str_value = 'tyjiayou'.encode('utf-8')
    str_struct = struct.pack(f'i f ? {len(str_value)}s', int_value, float_value, bool_value, str_value)
    print('info:', str_struct, '; len:', len(str_struct))
    # info: b'@\xe2\x01\x00\xd0\x0fI@\x01tyjiayou' ; len: 17
    

    一次解包多个:

    int_value, float_value, bool_value, str_value = struct.unpack(f'i f ? {len(str_value)}s', str_struct)
    print(int_value, float_value, bool_value, str_value)
    # 123456 3.141590118408203 True b'tyjiayou'
    

    处理二进制文件:

    # 写入二进制文件
    with open('data.bin', 'wb') as f:
        f.write(struct.pack('2i', 1, 2))
     
    # 读取二进制文件
    with open('data.bin', 'rb') as f:
        data = f.read()
        unpacked_data = struct.unpack('2i', data)
        print(unpacked_data)  # 输出: (1, 2)
    

    socket传输:

    # 服务端
    import socket
    import struct
    
    msg = '要出传输的信息'.encode('utf-8')
    msg_len = len(msg)
    msg_len_struct = struct.pack('i', msg_len)
    
    # 创建socket对象
    serv_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    serv_socket.bind(('127.0.0.1', 8080))
    serv_socket.listen(5)
    conn, addr = serv_socket.accept()
    
    # 先发送传输数据的大小
    conn.send(msg_len_struct)
    # 发送要传输的信息
    conn.sendall(msg)
    
    conn.close()
    serv_socket.close()
    
    
    
    # 客户端
    import socket
    import struct
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client_socket.connect(('127.0.0.1', 8080))
    
    # 获取msg_len
    head = client_socket.recv(4)
    msg_len = struct.unpack('i', head)[0]
    
    # 接收数据
    data_len = 0
    datas = b''
    while data_len < msg_len:
        data = client_socket.recv(1024)
        datas += data
        data_len += len(data)
    
    print(datas.decode('utf-8'))
    client_socket.close()
    

    一些特殊的情况

    尝试打包一个对于所定义字段来说过大的整数:

    pack(">h", 99999)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    struct.error: 'h' format requires -32768 <= number <= 32767
    

    显示 's' and 'c' 格式字符之间的差异:

    pack("@ccc", b'1', b'2', b'3')
    b'123'
    pack("@3s", b'123')
    b'123'
    

    解包的字段可通过将它们赋值给变量或将结果包装为一个具名元组来命名:

    from struct import *
    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)
    

    计算出对应于 format 的结构大小:

    s1 = struct.Struct('I3sf')
    s1.size 
    #12,python实现中存在内部对其或填充的规则,导致实际大小比预期的大。(具体查看官方官方struct文档介绍-格式)
    s1 = struct.Struct('If3s')
    s1.size 
    #11
    

    总结

    struct模块提供了多种格式字符串,可以灵活地处理各种类型的数据:

  • 直接操作二进制数据,避免了不必要的类型转换和内存分配,提高了处理效率。

  • 通过指定字节顺序,可以确保在不同平台之间传输数据时的一致性。

  • 网络编程中,经常需要发送和接收二进制数据,struct模块可以帮助你打包和解包这些数据。

  • 在处理二进制文件时,可以使用struct模块来读取和写入文件中的数据。

  • 函数异常类:

  • pack()、unpack()、pack_into()、unpack_from()、iter_unpack()、calcsize()

  • struct.error

  • Struct

  • 格式化字符串:i、f、s。

    struct模块是Python中处理二进制数据的强大工具,它提供了灵活、高效和跨平台的数据打包和解包功能。

    参考链接:struct — 将字节串解读为打包的二进制数据 — Python 3.13.2 文档

    作者:棠越

    物联沃分享整理
    物联沃-IOTWORD物联网 » python struct模块

    发表回复