有时候需要处理二进制数据。例如把Python对象转换为二进制发送到网络上,或者从网络上接收二进制数据再转换为Python对象。Python的struct模块包括一些函数和约定用于处理字节序列和内置数据类型的函数和方法。
二进制数据在传输时有字节序列问题:常见的字节序列有大端序列(big-endian)、小端序列(little-endian)等。这一点在转换是需要注意。
struct模块
struct模块提供二进制和对象间打包、解包的函数。另外还有一个Struct
类,可以指定格式指示符以提前编辑处理方法,这类似于Python的正则模块提前把正则表达式编译以提高性能。
Struct在格式指示符的规定下,将Python对象打包(packing)成字节序列,以方便存储和发送到网络上。
在指定字节序列和格式指示符下,把字节序列还原为Python对象以方便应用,这个过程称为解包(unpacking)。
默认情况下,打包、解包使用内置C库的字节序列。可以在格式指示符前指定字节序列。
指示符 |
含义 |
@ |
内置顺序 |
= |
内置标准 |
< |
little-endian |
> |
big-endian |
! |
网络顺序 |
例子
下面的例子打包I 5s 3f
格式的数据。
1 2 3 4 5 6 7 8 9 10 11 12 13
| import struct import binascii import ctypes
s = struct.Struct('I 5s 3f') values = (100, b'hello', 1.2, 1.5, 1.7) packed_data = s.pack(*values) hex_packed_data = binascii.hexlify(packed_data)
print("original values:", values) print("format string:", s.format) print("size:", s.size, "bytes") print("packed value", hex_packed_data)
|
程序输出:
1 2 3 4
| original values: (100, b'hello', 1.2, 1.5, 1.7) format string: b'I 5s 3f' size: 24 bytes packed value b'6400000068656c6c6f0000009a99993f0000c03f9a99d93f'
|
解包数据
1 2
| data = binascii.unhexlify(hex_packed_data) print("unpacking data:", s.unpack(data))
|
输出
1
| unpacking data: (100, b'hello', 1.2000000476837158, 1.5, 1.7000000476837158)
|
注意到浮点数的精度是损失了。
指定字节序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import struct import binascii
endianness = [ ('@', 'native, native'), ('=', 'native, standard'), ('<', 'little-endian'), ('>', 'big-endian'), ('!', 'network'), ]
values = (1, 2, 3, b'hello')
for code, name in endianness: s = struct.Struct(code + '3I 5s') packed_data = s.pack(*values) print('format string:', s.format, 'for', name) print('size:', s.size, 'bytes') print('packed value:', binascii.hexlify(packed_data)) print('unpacked value:', s.unpack(packed_data))
|
查看输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| format string: b'@3I 5s' for native, native size: 17 bytes packed value: b'01000000020000000300000068656c6c6f' unpacked value: (1, 2, 3, b'hello') format string: b'=3I 5s' for native, standard size: 17 bytes packed value: b'01000000020000000300000068656c6c6f' unpacked value: (1, 2, 3, b'hello') format string: b'<3I 5s' for little-endian size: 17 bytes packed value: b'01000000020000000300000068656c6c6f' unpacked value: (1, 2, 3, b'hello') format string: b'>3I 5s' for big-endian size: 17 bytes packed value: b'00000001000000020000000368656c6c6f' unpacked value: (1, 2, 3, b'hello') format string: b'!3I 5s' for network size: 17 bytes packed value: b'00000001000000020000000368656c6c6f' unpacked value: (1, 2, 3, b'hello')
|
指定缓冲区
如此程序重视性能,应该提前分配内存而不是动态分配,以减少碎片内存分配过程的开销。
下面的例子使用两种缓冲方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import struct import binascii import ctypes import array
values = (1, 2, 3, b'hello') s = struct.Struct('@3I 5s') string_buffer = ctypes.create_string_buffer(s.size) array_buffer = array.array('B', b'\0' * s.size)
print('ctypes string buffer') print('before:', binascii.hexlify(string_buffer.raw)) s.pack_into(string_buffer, 0, *values) print('after:', binascii.hexlify(string_buffer.raw)) print('unpacked:', s.unpack_from(string_buffer, 0)) print('-'*20)
print('array buffer') print('before:', binascii.hexlify(array_buffer)) s.pack_into(array_buffer, 0, *values) print('after:', binascii.hexlify(array_buffer)) print('unpacked:', s.unpack_from(array_buffer, 0))
|
输出
1 2 3 4 5 6 7 8 9
| ctypes string buffer before: b'0000000000000000000000000000000000' after: b'01000000020000000300000068656c6c6f' unpacked: (1, 2, 3, b'hello') -------------------- array buffer before: b'0000000000000000000000000000000000' after: b'01000000020000000300000068656c6c6f' unpacked: (1, 2, 3, b'hello')
|
转载请包括本文地址:https://allenwind.github.io/blog/5330
更多文章请参考:https://allenwind.github.io/blog/archives/