写点什么

Python 基础之:struct 和格式化字符

发布于: 2021 年 04 月 16 日

简介

文件的存储内容有两种方式,一种是二进制,一种是文本的形式。如果是以文本的形式存储在文件中,那么从文件中读取的时候就会遇到一个将文本转换为 Python 中数据类型的问题。实际上即使是文本的形式存储,存储的数据也是也是有结构的,因为 Python 底层是用 C 来编写的,这里我们也称之为 C 结构。

Lib/struct.py 就是负责进行这种结构转换的模块。

struct 中的方法

先看下 struct 的定义:

__all__ = [    # Functions    'calcsize', 'pack', 'pack_into', 'unpack', 'unpack_from',    'iter_unpack',
# Classes 'Struct',
# Exceptions 'error' ]
复制代码

其中有 6 个方法,1 个异常。

我们主要来看这 6 个方法的使用:

这些方法主要就是打包和解包的操作,其中一个非常重要的参数就是 format,也被成为格式字符串,它指定了每个字符串被打包的格式。

格式字符串

格式字符串是用来在打包和解包数据时指定数据格式的机制。 它们使用指定被打包/解包数据类型的 格式字符 进行构建。 此外,还有一些特殊字符用来控制 字节顺序,大小和对齐方式。

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

默认情况下,C 类型以机器的本机格式和字节顺序表示,并在必要时通过填充字节进行正确对齐(根据 C 编译器使用的规则)。

我们也可以手动指定格式字符串的字节顺序,大小和对齐方式:

大端和小端是两种数据存储方式。

第一种 Big Endian 将高位的字节存储在起始地址


第二种 Little Endian 将地位的字节存储在起始地址


其实 Big Endian 更加符合人类的读写习惯,而 Little Endian 更加符合机器的读写习惯。

目前主流的两大 CPU 阵营中,PowerPC 系列采用 big endian 方式存储数据,而 x86 系列则采用 little endian 方式存储数据。

如果不同的 CPU 架构直接进行通信,就由可能因为读取顺序的不同而产生问题。

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

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

格式字符

我们来看下字符都有哪些格式:

格式数字

举个例子,比如我们要打包一个 int 对象,我们可以这样写:

In [101]: from struct import *
In [102]: pack('i',10)Out[102]: b'\n\x00\x00\x00'
In [103]: unpack('i',b'\n\x00\x00\x00')Out[103]: (10,)
In [105]: calcsize('i')Out[105]: 4
复制代码

上面的例子中,我们打包了一个 int 对象 10,然后又对其解包。并且计算了 i 这个格式的长度为 4 字节。

大家可以看到输出结果是 b'\n\x00\x00\x00' ,这里不去深究这个输出到底是什么意思,开头的 b 表示的是 byte,后面是 byte 的编码。

格式字符之前可以带有整数重复计数。 例如,格式字符串 '4h' 的含义与 'hhhh' 完全相同。

看下如何打包 4 个 short 类型:

In [106]: pack('4h',2,3,4,5)Out[106]: b'\x02\x00\x03\x00\x04\x00\x05\x00'
In [107]: unpack('4h',b'\x02\x00\x03\x00\x04\x00\x05\x00')Out[107]: (2, 3, 4, 5)

复制代码

格式之间的空白字符会被忽略,但如果是 struct.calcsize 方法的话格式字符中不可有空白字符。

当使用某一种整数格式 (‘b’, ‘B’, ‘h’, ‘H’, ‘i’, ‘I’, ‘l’, ‘L’, ‘q’, ‘Q’) 打包值 x 时,如果 x 在该格式的有效范围之外则将引发 struct.error。

格式字符

除了数字之外,最常用的就是字符和字符串了。

我们先看下怎么使用格式字符,因为字符的长度是 1 个字节,我们需要这样做:

In [109]: pack('4c',b'a',b'b',b'c',b'd')Out[109]: b'abcd'
In [110]: unpack('4c',b'abcd')Out[110]: (b'a', b'b', b'c', b'd')
In [111]: calcsize('4c')Out[111]: 4
复制代码

字符前面的 b,表示这是一个字符,否则将会被当做字符串。

格式字符串

再看下字符串的格式:

In [114]: pack('4s',b'abcd')Out[114]: b'abcd'
In [115]: unpack('4s',b'abcd')Out[115]: (b'abcd',)
In [116]: calcsize('4s')Out[116]: 4
In [117]: calcsize('s')Out[117]: 1
复制代码

可以看到对于字符串来说 calcsize 返回的是字节的长度。

填充的影响

格式字符的顺序可能对大小产生影响,因为满足对齐要求所需的填充是不同的:

>>> pack('ci', b'*', 0x12131415)b'*\x00\x00\x00\x12\x13\x14\x15'>>> pack('ic', 0x12131415, b'*')b'\x12\x13\x14\x15*'>>> calcsize('ci')8>>> calcsize('ic')5
复制代码

下面的例子我们将会展示如何手动影响填充效果:

In [120]: pack('llh',1, 2, 3)Out[120]: b'\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00'
复制代码

上面的例子中,我们打包 1,2,3 这三个数字,但是格式不一样,分别是 long,long,short。

因为 long 是 4 个字节,short 是 2 个字节,所以本质上是不对齐的。

如果想要对齐,我们可以在后面再加上 0l 表示 0 个 long,从而进行手动填充:

In [118]: pack('llh0l', 1, 2, 3)Out[118]: b'\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00'
In [122]: unpack('llh0l',b'\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00')Out[122]: (1, 2, 3)
复制代码

复杂应用

最后看一个复杂点的应用,这个应用中直接从 unpack 出来的数据读取到元组中:

>>> 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)
复制代码

本文已收录于 http://www.flydean.com/13-python-struct-format-char/

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

发布于: 2021 年 04 月 16 日阅读数: 18
用户头像

关注公众号:程序那些事,更多精彩等着你! 2020.06.07 加入

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧,尽在公众号:程序那些事!

评论

发布
暂无评论
Python基础之:struct和格式化字符