A-A+

python 字节、二进制随意转换struct

2018年12月21日 15:24 汪洋大海 暂无评论 共698字 (阅读1,912 views次)

准确地讲,Python没有专门处理字节的数据类型。但由于b'str'可以表示字节,所以,字节数组=二进制str。而在C语言中,我们可以很方便地用struct、union来处理字节,以及字节和int,float的转换。

在Python中,比方说要把一个32位无符号整数变成字节,也就是4个长度的bytes,你得配合位运算符这么写:

>>> 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'

非常麻烦。如果换成浮点数就无能为力了。

好在Python提供了一个struct模块来解决bytes和其他二进制数据类型的转换。

structpack函数把任意数据类型变成bytes

>>> import struct
>>> struct.pack('>I', 10240099)
b'\x00\x9c@c'

pack的第一个参数是处理指令,'>I'的意思是:

>表示字节顺序是big-endian,也就是网络序,I表示4字节无符号整数。

这里要说一下big-endian,表示或关于计算机存储器中的数据排序系统,其中最重要的(大端)或最不重要的(小端)字节被放在第一位。

1
2
3
4
5
6
7
8
9
##这里要注意L值能计算4个字节,下方的b代表字节的意思,内容是字节格式
 
struct.unpack('<L', b'a3.7')[0]	=	925774689
struct.pack('<L',925774689)		=	b'a3.7'
struct.pack('>L',925774689)		=	b'7.3a'
 
struct.unpack('>L', b'a3.7')[0]	=	1630744119
struct.pack('>L',1630744119)	=	b'a3.7'
struct.pack('<L',1630744119)	=	b'7.3a'

后面的参数个数要和处理指令一致。

unpackbytes变成相应的数据类型:

>>> struct.unpack('>IH', b'\xf0\xf0\xf0\xf0\x80\x80')
(4042322160, 32896)

根据>IH的说明,后面的bytes依次变为I:4字节无符号整数和H:2字节无符号整数。

所以,尽管Python不适合编写底层操作字节流的代码,但在对性能要求不高的地方,利用struct就方便多了。

struct模块定义的数据类型可以参考Python官方文档:

https://docs.python.org/3/library/struct.html#format-characters

Windows的位图文件(.bmp)是一种非常简单的文件格式,我们来用struct分析一下。

首先找一个bmp文件,没有的话用“画图”画一个。

读入前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位图; 一个4字节整数:表示位图大小; 一个4字节整数:保留位,始终为0; 一个4字节整数:实际图像的偏移量; 一个4字节整数:Header的字节数; 一个4字节整数:图像宽度; 一个4字节整数:图像高度; 一个2字节整数:始终为1; 一个2字节整数:颜色数。

所以,组合起来用unpack读取:

>>> struct.unpack('<ccIIIIIIHH', s)
(b'B', b'M', 691256, 0, 54, 40, 640, 360, 1, 24)

结果显示,b'B'b'M'说明是Windows位图,位图大小为640x360,颜色数为24。

请编写一个bmpinfo.py,可以检查任意文件是否是位图文件,如果是,打印出图片大小和颜色数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# -*- coding: utf-8 -*-
import base64, struct
bmp_data = base64.b64decode('Qk1oAgAAAAAAADYAAAAoAAAAHAAAAAoAAAABABAAAAAAADICAAASCwAAEgsAAAAAAAAAAAAA/3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9/AHwAfAB8AHwAfAB8AHwAfP9//3//fwB8AHwAfAB8/3//f/9/AHwAfAB8AHz/f/9//3//f/9//38AfAB8AHwAfAB8AHwAfAB8AHz/f/9//38AfAB8/3//f/9//3//fwB8AHz/f/9//3//f/9//3//f/9/AHwAfP9//3//f/9/AHwAfP9//3//fwB8AHz/f/9//3//f/9/AHwAfP9//3//f/9//3//f/9//38AfAB8AHwAfAB8AHwAfP9//3//f/9/AHwAfP9//3//f/9//38AfAB8/3//f/9//3//f/9//3//fwB8AHwAfAB8AHwAfAB8/3//f/9//38AfAB8/3//f/9//3//fwB8AHz/f/9//3//f/9//3//f/9/AHwAfP9//3//f/9/AHwAfP9//3//fwB8AHz/f/9/AHz/f/9/AHwAfP9//38AfP9//3//f/9/AHwAfAB8AHwAfAB8AHwAfAB8/3//f/9/AHwAfP9//38AfAB8AHwAfAB8AHwAfAB8/3//f/9//38AfAB8AHwAfAB8AHwAfAB8/3//f/9/AHwAfAB8AHz/fwB8AHwAfAB8AHwAfAB8AHz/f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//38AAA==')
 
def bmp_info(data):
    return {
        'width': 200,
        'height': 100,
        'color': 24
    }
 
# 测试
bi = bmp_info(bmp_data)
assert bi['width'] == 28
assert bi['height'] == 10
assert bi['color'] == 16
print('ok')

struct- 将字节解释为压缩二进制数据

源代码: Lib / struct.py


此模块执行Python值和表示为Python bytes对象的C结构之间的转换。这可用于处理存储在文件中的二进制数据或来自网络连接以及其他来源。它使用 Format Strings作为C结构布局的简洁描述以及与Python值的预期转换。

注意

 

默认情况下,打包给定C结构的结果包括填充字节,以便维护所涉及的C类型的正确对齐; 类似地,在拆包时考虑对齐。选择此行为是为了使压缩结构的字节与相应C结构的内存中的布局完全对应。要处理与平台无关的数据格式或省略隐式填充字节,请使用standard大小和对齐而不是 native大小和对齐:有关详细信息请参阅字节顺序,大小和对齐

几个struct函数(和方法Struct)采用缓冲区 参数。这指的是实现缓冲区协议并提供可读或可读写缓冲区的对象。用于此目的的最常见类型是bytesbytearray,但是可以被视为字节数组的许多其他类型实现缓冲区协议,因此可以在不从bytes对象进行额外复制的情况下读取/填充它们。

功能和例外

该模块定义了以下异常和函数:

异常struct.error

在各种场合提出的例外情况; argument是一个描述错误的字符串。

struct.pack格式v1v2... 

返回包含值v1v2,... 的字节对象,根据格式字符串格式打包。参数必须与格式所需的值完全匹配。

struct.pack_into格式缓冲区偏移量v1v2... 

根据格式字符串格式打包值v1v2,... 并将打包字节写入从位置偏移开始的可写缓冲区缓冲区。请注意,offset是必需参数。

struct.unpack格式缓冲区

根据格式字符串格式从缓冲区缓冲区(可能由其打包)解压缩。结果是一个元组,即使它只包含一个项目。缓冲区的大小(以字节为单位)必须与格式所需的大小相匹配,如下所示。pack(format, ...)calcsize()

struct.unpack_from格式缓冲区偏移= 0 

根据格式字符串格式从位置偏移处开始从缓冲区解包。结果是一个元组,即使它只包含一个项目。缓冲区的大小(以字节为单位,减去偏移量)必须至少为格式所需的大小,如下所示。calcsize()

struct.iter_unpack格式缓冲区

根据格式字符串格式从缓冲区缓冲区中迭代解压缩。此函数返回一个迭代器,它将从缓冲区读取大小相同的块,直到其所有内容都被消耗掉。缓冲区的大小(以字节为单位)必须是格式所需大小的倍数,如下所示。calcsize()

每次迭代都会产生格式字符串指定的元组。

版本3.4中的新功能。

struct.calcsize格式

返回与格式字符串格式对应的struct(以及生成的bytes对象)的大小 。pack(format, ...)

格式化字符串

格式字符串是用于在打包和解包数据时指定预期布局的机制。它们由格式字符构成,它指定打包/解包的数据类型。此外,还有用于控制字节顺序,大小和对齐的特殊字符。

字节顺序,大小和对齐

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

或者,格式字符串的第一个字符可用于指示打包数据的字节顺序,大小和对齐方式,如下表所示:

字符 字节顺序 尺寸 对准
  @        本地人 本地人 本地人
  =        本地人 标准 没有
  <        小尾数 标准 没有
  >        大端 标准 没有
  !        网络(=大端) 标准 没有

如果第一个字符不是其中之一,'@'则假设。

本机字节顺序是big-endian或little-endian,具体取决于主机系统。例如,Intel x86和AMD64(x86-64)是little-endian; 摩托罗拉68000和PowerPC G5都是大端的; ARM和Intel Itanium具有可切换的字节序(双端)。使用sys.byteorder来检查你的系统的字节序。

使用C编译器的sizeof表达式确定本机大小和对齐 。这始终与本机字节顺序相结合。

标准尺寸仅取决于格式字符; 请参阅格式字符部分中的表。

需要注意的区别'@''=':都使用本地字节顺序,但后者的大小和排列是标准化的。

该表格'!'适用于那些声称无法记住网络字节顺序是big-endian还是little-endian的可怜人。

无法指示非本机字节顺序(强制字节交换); 使用适当的选择'<''>'

笔记:

  1. 填充仅在连续的结构成员之间自动添加。在编码结构的开头或结尾没有添加填充。
  2. 使用非原生大小和对齐时不添加填充,例如使用'<','>','='和'!'。
  3. 要将结构的末尾与特定类型的对齐要求对齐,请使用重复计数为零的该类型的代码结束格式。见例子

格式字符

格式字符具有以下含义; 根据类型,C和Python值之间的转换应该是显而易见的。“标准大小”列是指使用标准大小时打包值的大小(以字节为单位); 也就是说,当格式字符串中的一个开始'<''>''!'或 '='。使用本机大小时,打包值的大小取决于平台。

格式 C型 Python类型 标准尺寸 笔记
x 填充字节 没有价值    
c char 长度为1的字节 1  
b signed char 整数 1 (1),(3)
B unsigned char 整数 1 (3)
? _Bool 布尔 1 (1)
h short 整数 2 (3)
H unsigned short 整数 2 (3)
i int 整数 4 (3)
I unsigned int 整数 4 (3)
l long 整数 4 (3)
L unsigned long 整数 4 (3)
q long long 整数 8 (2),(3)
Q unsigned long long 整数 8 (2),(3)
n ssize_t 整数   (4)
N size_t 整数   (4)
e (7) 浮动 2 (5)
f float 浮动 4 (5)
d double 浮动 8 (5)
s char[] 字节    
p char[] 字节    
P void * 整数   (6)

改变在3.3版本:为新增支持'n''N'格式。

版本3.6中已更改:添加了对'e'格式的支持。

笔记:

  1. '?'转换码对应于_Bool由C99定义的类型。如果此类型不可用,则使用a进行模拟char。在标准模式下,它始终由一个字节表示。

  2. 'q''Q'只有在平台C编译器支持C转换代码在本地模式中可用,或者在Windows上, 。它们始终以标准模式提供。long long__int64

  3. 当尝试使用任何整数转换代码打包非整数时,如果非整数具有__index__()方法,则调用该方法以在打包之前将参数转换为整数。

    在版本3.2中更改:__index__()在非整数中使用该方法是3.2中的新增内容。

  4. 'n''N'转换码只适用于本机的大小(选择为默认或与'@'字节顺序字符)。对于标准大小,您可以使用适合您的应用程序的其他整数格式。

  5. 对于'f''d''e'转换码,填充表示使用IEEE 754 binary32,binary64或binary16格式('f''d''e'分别地),而不管由所述平台中使用的浮点格式的。

  6. 'P'格式字符仅适用于本地字节顺序(选择为默认或与'@'字节顺序字符)。字节顺序字符'='选择使用基于主机系统的小端或大端排序。struct模块不会将其解释为本机排序,因此'P'格式不可用。

  7. IEEE 754二进制16“半精度”类型是在2008年版的IEEE 754标准中引入的。它有一个符号位,一个5位指数和11位精度(显式存储10位),并且可以表示大约6.1e-056.5e+04 全精度之间的数字。C编译器不广泛支持这种类型:在典型的机器上,unsigned short可用于存储,但不能用于数学运算。有关详细信息,请参阅有关半精度浮点格式的Wikipedia页面。

格式字符之前可以是整数重复计数。例如,格式字符串'4h'表示与...完全相同'hhhh'

格式之间的空白字符被忽略; 计数及其格式不得包含空格。

对于's'格式字符,计数被解释为字节的长度,而不是像其他格式字符那样的重复计数; 例如, '10s'表示单个10字节字符串,而'10c'表示10个字符。如果未给出计数,则默认为1.对于打包,将截断字符串或​​使用空字节填充,以使其适合。对于解包,生成的bytes对象始终具有指定的字节数。作为一种特殊情况,'0s'表示单个空字符串(同时 '0c'表示0个字符)。

当包装一个值x使用的整数格式(一个'b', 'B''h''H''i''I''l''L', 'q''Q'),如果x是在有效范围之外为该格式然后struct.error上升。

在3.1版中更改:在3.0中,一些整数格式包含超出范围的值而DeprecationWarning不是struct.error

'p'格式字符编码“帕斯卡串”,意思是存储在一个很短的可变长度的字符串的固定数目的字节,由计数给出。存储的第一个字节是字符串的长度,或255,取较小者。字符串的字节如下。如果传入的字符串 pack()太长(长于计数减1),则只count-1存储字符串的前导 字节。如果字符串短于 count-1,则用空字节填充,以便使用所有字节中的精确计数字节。请注意,对于unpack()中,'p'格式字符占用 count的字节,但返回的字符串不能包含超过255个字节。

对于'?'格式字符,返回值为True或 False。打包时,使用参数对象的真值。将打包本机或标准bool表示中的0或1,并且True在解包时将使用任何非零值。

示例

注意

 

所有示例都假定本机字节顺序,大小和与big-endian机器的对齐。

打包/解包三个整数的基本示例:

>>>
>>> from struct import *
>>> pack('hhl', 1, 2, 3)
b'\x00\x01\x00\x02\x00\x00\x00\x03'
>>> unpack('hhl', b'\x00\x01\x00\x02\x00\x00\x00\x03')
(1, 2, 3)
>>> calcsize('hhl')
8

解压缩的字段可以通过将它们分配给变量或通过将结果包装在命名元组中来命名:

>>>
>>> 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)

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

>>>
>>> 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

以下格式'llh0l'指定末尾的两个填充字节,假设long在4字节边界上对齐:

>>>
>>> pack('llh0l', 1, 2, 3)
b'\x00\x00\x00\x01\x00\x00\x00\x02\x00\x03\x00\x00'

这仅在原始大小和对齐生效时有效; 标准大小和对齐不强制任何对齐。

也可以看看

模 array
打包同类数据的二进制存储。
模 xdrlib
打包和拆包XDR数据。

struct模块还定义了以下类型:

class struct.Struct格式

返回一个新的Struct对象,它根据格式字符串格式写入和读取二进制数据。创建一个Struct对象并调用其方法比调用struct具有相同格式的函数更有效,因为格式字符串只需要编译一次。

注意

 

传递给最新格式字符串的编译版本 Struct和模块级函数被缓存,因此只使用少量格式字符串的程序不必担心重用单个 Struct实例。

编译的Struct对象支持以下方法和属性:

packv1v2... 

pack()函数相同,使用编译格式。(len(result)等于size。)

pack_into缓冲区偏移量v1v2...... 

pack_into()函数相同,使用编译格式。

unpack缓冲区

unpack()函数相同,使用编译格式。缓冲区的大小(以字节为单位)必须相等size

unpack_frombufferoffset = 0 

unpack_from()函数相同,使用编译格式。缓冲区的大小(以字节为单位,减去偏移量)必须至少为size

iter_unpack缓冲区

iter_unpack()函数相同,使用编译格式。缓冲区的大小(以字节为单位)必须是其倍数size

版本3.4中的新功能。

format

用于构造此Struct对象的格式字符串。

在版本3.7中更改:格式字符串类型现在str而不是bytes

size

计算出的struct的大小(以及因此由pack()方法生成的bytes对象)对应于format

 

文章来源:https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001431955007656a66f831e208e4c189b8a9e9f3f25ba53000

https://docs.python.org/3/library/struct.html#format-characters

布施恩德可便相知重

微信扫一扫打赏

支付宝扫一扫打赏

×
标签:

给我留言