3.8. struct — 二进制数据结构
用途: 在 Python 基本数据类型和二进制数据之间进行转换。
struct
模块提供了用于在字节字符串和 Python 原生数据类型之间转换函数,比如数字和字符串。
模块函数和 Struct 类
它除了提供一个 Struct
类之外,还有许多模块级的函数用于处理结构化的值。这里有个格式符 (Format specifiers) 的概念,是指从字符串格式转换为已编译的表示形式,类似于正则表达式的处理方式。通常实例化 Struct
类,调用类方法来完成转换,比直接调用模块函数有效的多。下面的例子都是使用 Struct
类。
Packing (打包) 和 Unpacking (解包)
Struct
支持将数据 packing (打包) 成字符串,并能从字符串中逆向unpacking (解压) 出数据。请参阅标准库文档以获取支持的格式说明符的完整列表。
在这个例子中,格式指定器 (specifier) 需要一个整型或长整型,一个两个字节的 string,和一个浮点数。格式符中的空格用于分隔各个指示器(indicators),在编译格式时会被忽略。
struct_pack.py
import struct
import binascii
values = (1, 'ab'.encode('utf-8'), 2.7)
s = struct.Struct('I 2s f')
packed_data = s.pack(*values)
print('原始值:', values)
print('格式符:', s.format)
print('占用字节:', s.size, 'bytes')
print('打包结果:', binascii.hexlify(packed_data))
这个示例将打包的值转换为十六进制字节序列,用 binascii.hexlify()
方法打印出来。
$ python3 struct_pack.py
原始值: (1, b'ab', 2.7)
格式符: b'I 2s f'
占用字节: 12 bytes
打包结果: b'0100000061620000cdcc2c40'
使用 unpack()
方法解包。
struct_unpack.py
import struct
import binascii
packed_data = binascii.unhexlify(b'0100000061620000cdcc2c40')
s = struct.Struct('I 2s f')
unpacked_data = s.unpack(packed_data)
print('解包结果', unpacked_data)
将打包的值传给 unpack()
,基本上返回相同的值(浮点数会有差异)。
$ python3 struct_unpack.py
Unpacked Values: (1, b'ab', 2.700000047683716)
字节顺序
通常来说,包的值是通过本地的 C 库的字符顺序来编码的。通过在格式化字符串中提供准确的字符顺序指令,也可以很简单的重写它。
struct_endianness.py
import struct
import binascii
values = (1, 'ab'.encode('utf-8'), 2.7)
print('原始值 :', values)
endianness = [
('@', 'native, native'),
('=', 'native, standard'),
('<', 'little-endian'),
('>', 'big-endian'),
('!', 'network'),
]
for code, name in endianness:
s = struct.Struct(code + ' I 2s f')
packed_data = s.pack(*values)
print()
print('格式符 :', s.format, 'for', name)
print('占用字节 :', s.size, 'bytes')
print('打包结果 :', binascii.hexlify(packed_data))
print('解包结果 :', s.unpack(packed_data))
下表列出了 Struct
模块使用的字节顺序说明符。
struct 字节顺序说明符
Code | Meaning |
---|---|
@ |
本地顺序 |
= |
本地标准 |
< |
小字节序 |
> |
大字节序 |
! |
网络顺序 |
$ python3 struct_endianness.py
原始值: (1, b'ab', 2.7)
格式符 : b'@ I 2s f' for native, native
占用字节 : 12 bytes
打包结果 : b'0100000061620000cdcc2c40'
解包结果 : (1, b'ab', 2.700000047683716)
格式符 : b'= I 2s f' for native, standard
占用字节 : 10 bytes
打包结果 : b'010000006162cdcc2c40'
解包结果 : (1, b'ab', 2.700000047683716)
格式符 : b'< I 2s f' for little-endian
占用字节 : 10 bytes
打包结果 : b'010000006162cdcc2c40'
解包结果 : (1, b'ab', 2.700000047683716)
格式符 : b'> I 2s f' for big-endian
占用字节 : 10 bytes
打包结果 : b'000000016162402ccccd'
解包结果 : (1, b'ab', 2.700000047683716)
格式符 : b'! I 2s f' for network
占用字节 : 10 bytes
打包结果 : b'000000016162402ccccd'
解包结果 : (1, b'ab', 2.700000047683716)
缓冲区
将数据打包成二进制通常是用在对性能要求很高的场景。在这类场景中可以通过避免为每个打包结构分配新缓冲区的开销来优化。pack_into()
和 unpack_from()
方法支持直接写入预先分配的缓冲区。
struct_buffers.py
import array
import binascii
import ctypes
import struct
s = struct.Struct('I 2s f')
values = (1, 'ab'.encode('utf-8'), 2.7)
print('原始值:', values)
print()
print('使用了ctypes 模块的字符串缓冲区')
b = ctypes.create_string_buffer(s.size)
print('原始 buffer :', binascii.hexlify(b.raw))
s.pack_into(b, 0, *values)
print('打包结果写入 :', binascii.hexlify(b.raw))
print('解包 :', s.unpack_from(b, 0))
print()
print('使用了 array 模块')
a = array.array('b', b'\0' * s.size)
print('原始值 :', binascii.hexlify(a))
s.pack_into(a, 0, *values)
print('打包写入 :', binascii.hexlify(a))
print('解包 :', s.unpack_from(a, 0))
Struct
的 size
属性告诉我们缓冲区需要有多达。
$ python3 struct_buffers.py
原始值 : (1, b'ab', 2.7)
使用了ctypes 模块的字符串缓冲区
原始 buffer : b'000000000000000000000000'
打包结果写入 : b'0100000061620000cdcc2c40'
解包 : (1, b'ab', 2.700000047683716)
使用了 array 模块
原始 buffer : b'000000000000000000000000'
打包结果写入 : b'0100000061620000cdcc2c40'
解包 : (1, b'ab', 2.700000047683716)
参考
- struct 的标准库文档
- Python 2 到 3 的移植结构笔记
array
--array
模块,用于处理固定类型值的序列。binascii
--binascii
模块,用于生成二进制数据的 ASCII 表。- Wiki百科: 字符顺序 -- 编码中字节顺序的说明。
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。