Python数据结构与变量长度控制:深入理解int, float, str, list, dict, tuple及struct模块的应用

为了保持语言的简单性,Python只为用户提供了几种简单的数据结构:int、float、str、list、dict和tuple。与编译型语言C/C++不同,在Python中,我们往往不需要关心不同类型的变量在解释器内部是如何实现的。例如,对于一个长整型数据,我们可以在Python 2中直接写a=123456789012345L,而不必考虑变量a占用了多少字节。这个抽象方法为程序编写提供了足够的支持,但是在某些情况下(例如读写二进制文件、网络Raw Socket编程),我们需要一些其他模块来实现我们的变长控制需求。

Python数据结构与变量长度控制:深入理解int, float, str, list, dict, tuple及struct模块的应用

结构模块

当我们在Python中处理二进制数据时,我们需要使用struct模块。 struct 模块为 Python 和 C 的混合编程、处理二进制文件以及与网络协议交互提供了便利。理解这个模块主要需要理解三个功能:

struct.pack(fmt, v1, v2, ...)
struct.unpack(fmt, string)
struct.calcsize(fmt)

第一个函数包负责将不同的变量打包在一起成为一个字节串,类似于C语言中的字节流。第二个函数 unpack 将字节字符串解包到变量中。第三个函数 calsize 计算以 fmt 格式打包的结果有多少字节。这里的打包格式fmt决定了如何将变量打包成字节流,字节流中包含一系列格式字符串。

关于格式字符串

Python手册中给出了C语言和Python类型中常见类型对应的格式符号:

struct.pack(fmt, v1, v2, ...)

返回一个字符串,其中包含根据给定格式打包的值 v1, v2, ...。参数必须与格式所需的值完全匹配。

struct.pack用于根据格式字符(准确地说是Byte)将Python值转换为字符串。我们之前已经提到过,Python3中的unicode和bytes。在Py3中,文本始终是Unicode,由str类型表示,二进制数据由bytes类型表示。

Py2 中没有 Byte 这样的东西。参数fmt是格式字符串,v1,v2,...代表要转换的python值。以下示例将两个整数转换为字符串:

import struct 
a = 20 
b = 400
byte = struct.pack("ii", a, b) #转换后的str相当于其他语言中的字节流(字节数组),可以在网络上传输 
big = struct.pack(">ii", a, b) #大端保存
small = struct.pack(">>:b'\x14\x00\x00\x00\x90\x01\x00\x00'
print(big)
# >>>:b'\x00\x00\x00\x14\x00\x00\x01\x90'
print(small)
# >>>:b'\x14\x00\x00\x00\x90\x01\x00\x00'
print (byte[0],byte[4]) 
# >>>:b'\x14\x00\x00\x00\x90\x01\x00\x00'

格式字符“i”表示转换为int,“ii”表示有两个int变量。转换后的结果长度为8个字节(int类型占用4个字节,两个int为8个字节)。可以看到输出结果是乱码,因为结果是二进制数据,所以显示为乱码。可以使用python的内置函数repr来获取可识别的字符串。在Python3中不会出现上述问题。十六进制0x00000014和0x00000190分别代表20和400。

前面代码的最后一部分非常有趣,它实际上默认使用小端。

大端存储和小端存储

Little endian:较高的有效字节存储在较高的内存地址中,较低的有效字节存储在较低的内存地址中。

Big Endian:高位有效字节存储在低位内存地址中,低位有效字节存储在高位内存地址中。

如果一个16位整数0x1234存储在短整型变量(short)中。该短整型变量以字节序模式存储在内存中,如下表所示。

地址偏移 大端模式 小端模式 0x00 12 (OP0) 34 (OP1) 0x01 34 (OP1) 12 (OP0)

采用big-endian方式进行数据存储符合人类正常思维,而采用little-endian方式进行数据存储则有利于计算机处理。

struct.unpack(fmt, 缓冲区)

根据格式字符串 fmt 从缓冲区 buffer 中解包(大概由 pack(fmt, ...) 打包)。结果是一个元组,即使它只包含一项。缓冲区的大小(以字节为单位)必须与格式所需的大小相匹配,如 calcsize() 所反映的那样。

struct.unpack 的作用与 struct.pack 相反,用于将字节流转换为 Python 数据类型。其函数原型为:struct.unpack(fmt, string),返回一个元组。

import struct
a = struct.pack("2I3sI", 12, 34, "abc", 56)
b = struct.unpack("2I3sI", a)
print b
## 输出 (12, 34, 'abc', 56)

struct.calcsize(fmt)

返回与格式字符串 fmt 相对应的结构体的大小(以及由 pack(fmt, ...) 生成的字节对象的大小)。

struct.calcsize用于计算格式字符串对应的结果的长度,如:struct.calcsize('ii'),返回8。因为两个int类型占用的长度是8个字节。

使用struct打包固定长度的结构体

一般来说,使用struct时,要打包的数据是固定长度的。定长数据意味着需要显式指定要打包或解包的数据长度,否则打包和解包函数会出错。下面通过一个例子来说明什么是定长打包:

import struct
a = struct.pack("2I3sI", 12, 34, "abc", 56)
b = struct.unpack("2I3sI", a)
print b
## 输出 (12, 34, 'abc', 56)

上面的代码将两个整数12和34、一个字符串“abc”和一个整数56打包到一个字节字符流中,然后将它们解包。打包格式明确指出了打包的长度:“2I”表示起始点是两个unsigned int,“3s”表示长度为4的字符串,最后一个“I”表示起始点有一个unsigned int结尾。所以上面 print b 的输出是:(12, 34, 'abc', 56)。

我们可以调用 calcsize() 来计算“2I3sI”模式占用的字节数:

print struct.calcsize("2I3sI")
## 输出 16

可以看到上面三个整数加上一个3字符的字符串总共占用了16个字节。为什么是16字节?不应该是15字节吗?事实上,struct在打包过程中,必须根据具体类型的要求进行字节对齐。由于默认的unsigned int类型占用4个字节,因此字符串的位置需要4字节对齐,所以即使是3个字符的字符串也占用4个字节。

我们看一下不需要字节对齐的模式:

print struct.calcsize("2Is")
## 输出 9

由于单个字符出现在两个整数之后,因此不需要字节对齐,因此输出结果为9。

需要指出的是,对于unpack来说,只要fmt对应的字节数与字节串string的字节数一致,就可以解析成功,否则unpack函数会抛出异常。例如,我们还可以使用下面的fmt来解析a:

c = struct.unpack("2I2sI", a)
print struct.calcsize("2I2sI")
print c
## 输出 16 (12, 34, 'ab', 56)

您可以看到 unpack 解析了字符串的前两个字符,没有任何问题。

struct 处理可变长度数据

我们看到,在使用pack和unpack的过程中,我们需要显式地指明打包模式中每个位置的长度。例如,格式“2I3sI”清楚地表明了整数的个数和字符串的个数。有时,我们可能还需要处理变长打包数据。

可变长度字符串的打包

例如,我们可能在程序中得到一个字符串s。这个s没有固定的长度,所以我们每次打包的时候都需要将s的长度打包在一起,这样才能正确解包。事实上,这种情况在处理网络数据包时很常见。在使用网络编程时,我们可能会使用消息的第一个字段来记录消息的长度。我们每次读取消息时,都会先读取消息的第一个字段,获取其长度,然后处理消息内容。

我们可以通过两种方式处理这种情况:

s = bytes(s, 'utf-8') # Or other appropriate encoding
struct.pack("I%ds" % (len(s),), len(s), s)

或者

struct.pack("I", len(s)) + s

第一种方法首先将消息转换为字节码,然后获取字节码的长度,并将该长度嵌入到打包的消息中。可以看到格式字符串中的“I”是用来记录消息的长度的。第二种方法是直接将字符串的长度打包成字节串,然后与原字符串进行串联操作。

解压变长字符串

根据上面的打包方法,我们可以轻松解开打包串:

int_size = struct.calcsize("I")
(i,), data = struct.unpack("I", data[:int_size]), data[int_size:]
data_content = data[i:]

由于消息len(s)的长度我们使用定长整数“I”进行打包,因此解包时我们可以先获取消息长度,然后根据消息长度读取消息内容。

实现简单的自定义协议数据传输

在工作中,我曾经遇到过一种情况,需要后端服务生成多张图片并返回给前端js使用。第一种方案是使用base64将图片二进制文件转成字符串,打包返回给前端使用。私有协议分为以下三部分:

例如:3L80205_768037_128848base64strs

可以通过解析返回的数据头信息获取图片数据,并读取二进制长度生成多张图片。但在实际应用中,发现base64编码的二进制文件的文件大小增加了30%-50%,导致这样就消除了不必要的网络开销,优化为直接使用二进制来传输文件。核心代码如下:

以上就是利用struct进行数据打包,实现了简单版ziplist的功能。

你可能想看:
« 上一篇
下一篇 »

相关推荐

python爬虫教程PDF,Python爬虫书PDF

2026年09月02日

31阅读

外国教程python,python编程教学视频

2026年09月02日

25阅读

python简明教程沈洁元,python详细基础教程

2026年09月02日

21阅读

python库包教程,python 包

2026年09月02日

26阅读

python教程大全集,python教程600集

2026年09月02日

26阅读

python入门教程完整版,python零基础入门全套教程视频

2026年09月02日

22阅读