struct
为何需要struct
准确地讲,Python没有专门处理字节的数据类型。但由于 b'str'
可以用来表示字节,所以在Python中可以认为 字节数组=二进制str
。而在C语言中,我们可以很方便地用 struct
、union
等库来处理字节以及字节和int,float的转换。
假设我们要把一个32位无符号整数转换为字节(4个bytes)。在Python中,得这么写:
>>> n = 10240099
>>> b1 = (n & 0xff000000) >> 24
>>> b2 = (n & 0xff0000) >> 16
>>> b3 = (n & 0xff00) >> 8
>>> b4 = n & 0xff
>>> bs = bytes([b1, b2, b3, b4])
>>> bs
b'\x00\x9c@c'
稍微解析一下,十进制数 10240099
转换为十六进制是 0x009c4063
,这里使用与运算和右移来拆分出这个32位无符号整数的每一个byte(注意得到的不是字节数组而是一个整数),其中 b1=0, b2=156, b3=64, b4=99
,将这四个整数放入一个列表中传入 bytes()
函数,就能得到字节数组了。注意 bs
中是有4个字节的,len(bs)
的值为4。由于对应整数64的十六进制数 0x40
属于ASCII码范围,所以用字符 @
表示,而对应整数99的十六进制数 0x63
则对应字符 c
。
可以看到,这样要逐个byte来拆分,再进行转换实在是非常麻烦。如果要把浮点数转换为字节就无能为力了。幸好,Python提供了一个 struct
模块来解决 bytes
和其他二进制数据类型的转换问题。
struct的用法
struct
模块的 pack
函数把任意数据类型变成 bytes
:
>>> import struct
>>> struct.pack('>I', 10240099)
b'\x00\x9c@c'
pack()
的第一个参数是处理指令,这里 '>I'
的意思分为两部分:
- 字节顺序:
>
表示的是使用大端序作为字节顺序。 - 数据类型:
I
表示的是要转换一个32位无符号整数。可以有多个数据类型从而转换出多个数。
第二个参数要注意和处理指令一致。
unpack()
函数与 pack()
相反,它是把 bytes
转换为相应的数据类型:
>>> struct.unpack('>IH', b'\xf0\xf0\xf0\xf0\x80\x80')
(4042322160, 32896)
这里的 >IH
表示这个字节数组用的是大端序,并且要依次转换出一个32位无符号整数和16位无符号整数。
struct
模块定义的数据类型可以参考Python官方文档。关于大端序(big-endian,BE)和小端序(little-endian,LE)的区别可以看看这篇博文,讲解得很清晰。
使用struct分析bmp文件
Windows的位图文件(.bmp)是一种非常简单的文件格式,我们可以使用 struct
来分析一下。
首先找到一个bmp文件,没有的话用Windows自带的【画图】画一个即可。读入它的前30个字节来分析:
>>> s = b'\x42\x4d\x38\x8c\x0a\x00\x00\x00\x00\x00\x36\x00\x00\x00\x28\x00\x00\x00\x80\x02\x00\x00\x68\x01\x00\x00\x01\x00\x18\x00'
BMP格式采用小端序的方式存储数据,文件头的结构按顺序如下:
- 两个字节:'BM’表示Windows位图,'BA’表示OS/2位图;
- 一个32位无符号整数:表示位图大小;
- 一个32位无符号整数:保留位,始终为0;
- 一个32位无符号整数:实际图像的偏移量;
- 一个32位无符号整数:Header的字节数;
- 一个32位无符号整数:图像宽度(单位为像素);
- 一个32位无符号整数:图像高度(单位为像素);
- 一个16位无符号整数:始终为1;
- 一个16位无符号整数:颜色数。
所以,组合起来用unpack读取:
>>> struct.unpack('<ccIIIIIIHH', s)
(b'B', b'M', 691256, 0, 54, 40, 640, 360, 1, 24)
这里的 c
表示对应的数据类型是一个字符。结果显示,b'B'
、b'M'
说明是一张Windows位图,位图大小为640x360,颜色数为24。
小结
尽管Python不适合编写底层操作字节流的代码,但在对性能要求不高的地方,利用 struct
操作能够更加方便。
练习
请编写一个bmpinfo.py,可以检查任意文件是否是Windows位图文件,如果是,打印出图片大小和颜色数。
代码:
#-*- coding: utf-8 -*-
import sys
import struct
def readBmpFile(file):
f = open(file, 'rb')
bs = f.read()
f.close()
return bs[0:30]
def checkBmp(info):
ts = struct.unpack('<ccIIIIIIHH', info)
if ts[0] == b'B' and ts[1] == b'M':
print('图片大小:%d * %d' % (ts[6], ts[7]))
print('颜色数:%d' % ts[9])
else:
print('非位图文件')
if __name__=='__main__':
if len(sys.argv) == 2:
info = readBmpFile(sys.argv[1])
checkBmp(info)
else:
info = input('Please input the file name: ')
checkBmp(info)