Python标准库之struct(python struct库)
off999 2024-10-25 13:46 16 浏览 0 评论
Python 为了保持语言的简洁,仅为用户提供了几种简单的数据结构:int, float, str, list, dict,tuple。不同于编译型语言 C/C++,在 Python 中,我们往往不需要关心不同类型的变量在解释器内部的实现方式。例如,对于一个长整形数据,我们在 Python 2 中可以直接写成 a=123456789012345L,而不用去考虑变量 a 占了几个字节。这种抽象的方式为程序的编写提供了足够的支持,但是在某些情况下(比如读写二进制文件,进行网络 Raw Socket 编程)的时候,我们需要一些其他模块来实现我们关于变量长度控制的需求。
struct 模块
当我们在 Python 中跟二进制数据打交道的时候,就要用到 struct 这个模块了。struct 模块为 Python 与 C 的混合编程,处理二进制文件以及进行网络协议交互提供了便利。理解这个模块主要需要理解三个函数:
struct.pack(fmt, v1, v2, ...) struct.unpack(fmt, string) struct.calcsize(fmt)
第一个函数 pack 负责将不同的变量打包在一起,成为一个字节字符串,即类似于 C 语言中的字节流。第二个函数 unpack 将字节字符串解包成为变量。第三个函数 calsize 计算按照格式 fmt 打包的结果有多少个字节。这里打包格式 fmt 确定了将变量按照什么方式打包成字节流,其包含了一系列的格式字符串。
关于格式字符串
在Python手册中,给出了C语言中常用类型与Python类型对应的格式符:
struct.pack(fmt, v1, v2, ...)
Return a string containing the values v1, v2, ... packed according to the given format. The arguments must match the values required by the format exactly.
struct.pack用于将Python的值根据格式符,转换为字符串,准确来说是Byte。这个地方我们之前有提过,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("<ii", a, b) #小端保存 print(byte) # >>>: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。
上一段代码最后那个很有意思诶,竟然是默认采用小端。
大端存储和小端存储
小端:较高的有效字节存放在较高的存储器地址,较低的有效字节存放在较低的存储器地址。
大端:较高的有效字节存放在较低的存储器地址,较低的有效字节存放在较高的存储器地址。
如果将一个16位的整数0x1234存放到一个短整型变量(short)中。这个短整型变量在内存中的存储在大小端模式由下表所示。
地址偏移 大端模式 小端模式 0x00 12(OP0) 34(OP1) 0x01 34(OP1) 12(OP0)
采用大端方式进行数据存放符合人类的正常思维,而采用小端方式进行数据存放利于计算机处理。
struct.unpack(fmt, buffer)
Unpack from the buffer buffer (presumably packed by pack(fmt, ...)) according to the format string fmt. The result is a tuple even if it contains exactly one item. The buffer’s size in bytes must match the size required by the format, as reflected by calcsize().
struct.unpack做的工作刚好与struct.pack相反,用于将字节流转换成python数据类型。它的函数原型为:struct.unpack(fmt, string),该函数返回一个tuple。
import struct a = struct.pack("2I3sI", 12, 34, "abc", 56) b = struct.unpack("2I3sI", a) print b ## 输出 (12, 34, 'abc', 56)
struct.calcsize(fmt)
Return the size of the struct (and hence of the bytes object produced by pack(fmt, ...)) corresponding to the format string 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。所以上面的打印 b 输出结果是:(12, 34, ‘abc’, 56)。
我们可以调用 calcsize() 来计算 "2I3sI" 这个模式占用的字节数:
print struct.calcsize("2I3sI") ## 输出 16
可以看到上面的三个整型加一个 3 字符的字符串一共占用了 16 个字节。为什么会是 16 个字节呢?不应该是 15 个字节吗?其实,在 struct 的打包过程中,根据特定类型的要求,必须进行字节对齐。由于默认 unsigned int 型占用四个字节,因此要在字符串的位置进行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+Flask+Echarts 构建电商实时数据大屏
-
在电商运营中,实时掌握销售趋势、用户行为等核心数据是决策的关键。本文将从实战角度,详解如何用Python+Flask+Echarts技术栈,快速搭建一个支持实时更新、多维度可视化的电商数据大屏,帮...
- DeepSeek完全使用手册:从新手到高手的2000字实操指南
-
一、工具定位与核心功能矩阵(200字)DeepSeek是一款专注于深度推理的强大AI助手,其功能丰富多样,可归纳为4大能力象限:plaintext差异化优势:DeepSeek支持最长达16Ktok...
- Python绘制可爱的图表 cutecharts
-
一个很酷的python手绘样式可视化包——可爱的图表cutecharts。Cutecharts非常适合为图表提供更个性化的触感。Cutecharts与常规的Matplotlib和Seabo...
- 第十二章:Python与数据处理和可视化
-
12.1使用pandas进行数据处理12.1.1理论知识pandas是Python中最常用的数据处理库之一,它提供了高效的数据结构和数据分析工具。pandas的核心数据结构是Serie...
- 5分钟就能做一个Excel动态图表,你确定不学学?(纯gif教学)
-
本文说明下图是一个比较酷炫的Excel动态图表,最难的部分就是用到了一个复选框控件。其实这个控件我很早就见过,但是不会用呀!望洋兴叹。这次呢,我也是借着这个文章为大家讲述一下这个控件的使用。本文没有...
- Python数据可视化:从Pandas基础到Seaborn高级应用
-
数据可视化是数据分析中不可或缺的一环,它能帮助我们直观理解数据模式和趋势。本文将全面介绍Python中最常用的三种可视化方法。Pandas内置绘图功能Pandas基于Matplotlib提供了简洁的绘...
- 如何使用 Python 将图表写入 Excel
-
将Python生成的图表写入Excel文件是数据分析和可视化中常见的需求。Python提供了多种库(如matplotlib、openpyxl和xlsxwriter)来实现这一功能。本文...
- Excel 图表制作太痛苦?用 Python 生成动态交互图表
-
做个动态图表花了3小时?你该换方法了!上周帮销售部做季度汇报图表,Excel操作把我整崩溃了——插入折线图后发现数据源选错,重新选择又得调格式想做动态筛选图表,捣鼓"开发工具"...
- Python Matplotlib 入门教程:可视化数据的基石
-
一、简介Matplotlib是Python中最流行的数据可视化库,提供从简单折线图到复杂3D图形的完整解决方案。其核心优势在于:o灵活性强:支持像素级样式控制o兼容性好:与NumPy、Pa...
- 20种Python数据可视化绘图 直接复制可用
-
本文介绍20种python数据绘图方法,可直接用于科研绘图或汇报用图。1.折线图(LinePlot)-描述数据随时间或其他变量的变化。importmatplotlib.pyplotasp...
- Python os模块完全指南:轻松玩转文件管理与系统操作
-
Pythonos模块完全指南:轻松玩转文件管理与系统操作os模块是Python与操作系统对话的"瑞士军刀",学会它能让你轻松管理文件、操控路径、获取系统信息。本教程通过场景化案例+...
- Python中h5py与netCDF4模块在Anaconda环境的下载与安装
-
本文介绍基于Anaconda环境,下载并安装Python中h5py与netCDF4这两个模块的方法。h5py与netCDF4这两个模块是与遥感图像处理、地学分析等GIS操作息息相关的模块,应用...
- python中的模块、库、包有什么区别?
-
一文带你分清Python模块、包和库。一、模块Python模块(Module),是一个Python文件,以.py结尾,包含了Python对象定义和Python语句。模块能定义函数,类和变...
- centos7 下面使用源码编译的方式安装python3.11
-
centos7下面使用源码编译的方式安装python3.11,步骤如下:cd/root#只是将python3.11的安装包下载到/root目录下wgethttps://www.python.o...
- Python其实很简单 第十四章 模块
-
模块是一组程序代码,可以是别人已经写好的,也可以是自己编写的,但都是已经存在的,在编程时直接使用就可以了。模块机制的最大好处就是程序员不再编写重复的代码,而直接利用已有的成果,这样就能将更多的精力投入...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- python计时 (73)
- python安装路径 (56)
- python类型转换 (93)
- python进度条 (67)
- python吧 (67)
- python的for循环 (65)
- python格式化字符串 (61)
- python静态方法 (57)
- python列表切片 (59)
- python面向对象编程 (60)
- python 代码加密 (65)
- python串口编程 (77)
- python封装 (57)
- python读取文件夹下所有文件 (59)
- java调用python脚本 (56)
- python操作mysql数据库 (66)
- python获取列表的长度 (64)
- python接口 (63)
- python调用函数 (57)
- python多态 (60)
- python匿名函数 (59)
- python打印九九乘法表 (65)
- python赋值 (62)
- python异常 (69)
- python元祖 (57)