Python如何读写二进制数组数据


Posted in Python onAugust 01, 2020

问题

你想读写一个二进制数组的结构化数据到Python元组中。

解决方案

可以使用 struct 模块处理二进制数据。 下面是一段示例代码将一个Python元组列表写入一个二进制文件,并使用 struct 将每个元组编码为一个结构体。

from struct import Struct
def write_records(records, format, f):
  '''
  Write a sequence of tuples to a binary file of structures.
  '''
  record_struct = Struct(format)
  for r in records:
    f.write(record_struct.pack(*r))

# Example
if __name__ == '__main__':
  records = [ (1, 2.3, 4.5),
        (6, 7.8, 9.0),
        (12, 13.4, 56.7) ]
  with open('data.b', 'wb') as f:
    write_records(records, '<idd', f)

有很多种方法来读取这个文件并返回一个元组列表。 首先,如果你打算以块的形式增量读取文件,你可以这样做:

from struct import Struct

def read_records(format, f):
  record_struct = Struct(format)
  chunks = iter(lambda: f.read(record_struct.size), b'')
  return (record_struct.unpack(chunk) for chunk in chunks)

# Example
if __name__ == '__main__':
  with open('data.b','rb') as f:
    for rec in read_records('<idd', f):
      # Process rec
      ...

如果你想将整个文件一次性读取到一个字节字符串中,然后在分片解析。那么你可以这样做:

from struct import Struct

def unpack_records(format, data):
  record_struct = Struct(format)
  return (record_struct.unpack_from(data, offset)
      for offset in range(0, len(data), record_struct.size))

# Example
if __name__ == '__main__':
  with open('data.b', 'rb') as f:
    data = f.read()
  for rec in unpack_records('<idd', data):
    # Process rec
    ...

两种情况下的结果都是一个可返回用来创建该文件的原始元组的可迭代对象。

讨论

对于需要编码和解码二进制数据的程序而言,通常会使用 struct 模块。 为了声明一个新的结构体,只需要像这样创建一个 Struct 实例即可:

# Little endian 32-bit integer, two double precision floats
record_struct = Struct('<idd')

结构体通常会使用一些结构码值i, d, f等 [参考 Python文档 ]。 这些代码分别代表某个特定的二进制数据类型如32位整数,64位浮点数,32位浮点数等。 第一个字符 < 指定了字节顺序。在这个例子中,它表示”低位在前”。 更改这个字符为 > 表示高位在前,或者是 ! 表示网络字节顺序。

产生的 Struct 实例有很多属性和方法用来操作相应类型的结构。 size 属性包含了结构的字节数,这在I/O操作时非常有用。 pack() unpack() 方法被用来打包和解包数据。比如:

>>> from struct import Struct
>>> record_struct = Struct('<idd')
>>> record_struct.size
20
>>> record_struct.pack(1, 2.0, 3.0)
b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x08@'
>>> record_struct.unpack(_)
(1, 2.0, 3.0)
>>>

有时候你还会看到 pack() unpack() 操作以模块级别函数被调用,类似下面这样:

>>> import struct
>>> struct.pack('<idd', 1, 2.0, 3.0)
b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x08@'
>>> struct.unpack('<idd', _)
(1, 2.0, 3.0)
>>>

这样可以工作,但是感觉没有实例方法那么优雅,特别是在你代码中同样的结构出现在多个地方的时候。 通过创建一个 Struct 实例,格式代码只会指定一次并且所有的操作被集中处理。 这样一来代码维护就变得更加简单了(因为你只需要改变一处代码即可)。

读取二进制结构的代码要用到一些非常有趣而优美的编程技巧。 在函数 read_records 中,iter() 被用来创建一个返回固定大小数据块的迭代器。 这个迭代器会不断的调用一个用户提供的可调用对象(比如 lambda: f.read(record_struct.size) ), 直到它返回一个特殊的值(如b'‘),这时候迭代停止。例如:

>>> f = open('data.b', 'rb')
>>> chunks = iter(lambda: f.read(20), b'')
>>> chunks
<callable_iterator object at 0x10069e6d0>
>>> for chk in chunks:
... print(chk)
...
b'\x01\x00\x00\x00ffffff\x02@\x00\x00\x00\x00\x00\x00\x12@'
b'\x06\x00\x00\x00333333\x1f@\x00\x00\x00\x00\x00\x00"@'
b'\x0c\x00\x00\x00\xcd\xcc\xcc\xcc\xcc\xcc*@\x9a\x99\x99\x99\x99YL@'
>>>

如你所见,创建一个可迭代对象的一个原因是它能允许使用一个生成器推导来创建记录。 如果你不使用这种技术,那么代码可能会像下面这样:

def read_records(format, f):
  record_struct = Struct(format)
  while True:
    chk = f.read(record_struct.size)
    if chk == b'':
      break
    yield record_struct.unpack(chk)

在函数 unpack_records() 中使用了另外一种方法 unpack_from() 。 unpack_from() 对于从一个大型二进制数组中提取二进制数据非常有用, 因为它不会产生任何的临时对象或者进行内存复制操作。 你只需要给它一个字节字符串(或数组)和一个字节偏移量,它会从那个位置开始直接解包数据。

如果你使用 unpack() 来代替 unpack_from() , 你需要修改代码来构造大量的小的切片以及进行偏移量的计算。比如:

def unpack_records(format, data):
  record_struct = Struct(format)
  return (record_struct.unpack(data[offset:offset + record_struct.size])
      for offset in range(0, len(data), record_struct.size))

这种方案除了代码看上去很复杂外,还得做很多额外的工作,因为它执行了大量的偏移量计算, 复制数据以及构造小的切片对象。 如果你准备从读取到的一个大型字节字符串中解包大量的结构体的话,unpack_from() 会表现的更出色。

在解包的时候,collections 模块中的命名元组对象或许是你想要用到的。 它可以让你给返回元组设置属性名称。例如:

from collections import namedtuple

Record = namedtuple('Record', ['kind','x','y'])

with open('data.p', 'rb') as f:
  records = (Record(*r) for r in read_records('<idd', f))

for r in records:
  print(r.kind, r.x, r.y)

如果你的程序需要处理大量的二进制数据,你最好使用 numpy 模块。 例如,你可以将一个二进制数据读取到一个结构化数组中而不是一个元组列表中。就像下面这样:

>>> import numpy as np
>>> f = open('data.b', 'rb')
>>> records = np.fromfile(f, dtype='<i,<d,<d')
>>> records
array([(1, 2.3, 4.5), (6, 7.8, 9.0), (12, 13.4, 56.7)],
dtype=[('f0', '<i4'), ('f1', '<f8'), ('f2', '<f8')])
>>> records[0]
(1, 2.3, 4.5)
>>> records[1]
(6, 7.8, 9.0)
>>>

最后提一点,如果你需要从已知的文件格式(如图片格式,图形文件,HDF5等)中读取二进制数据时, 先检查看看Python是不是已经提供了现存的模块。因为不到万不得已没有必要去重复造轮子。

以上就是Python如何读写二进制数组数据的详细内容,更多关于Python读写二进制数组数据的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
Python使用multiprocessing创建进程的方法
Jun 04 Python
Python机器学习之SVM支持向量机
Dec 27 Python
python 通过字符串调用对象属性或方法的实例讲解
Apr 21 Python
Python爬虫抓取代理IP并检验可用性的实例
May 07 Python
详解Python if-elif-else知识点
Jun 11 Python
python爬虫基础教程:requests库(二)代码实例
Apr 09 Python
python lxml中etree的简单应用
May 10 Python
Python OpenCV中的resize()函数的使用
Jun 20 Python
python3 自动识别usb连接状态,即对usb重连的判断方法
Jul 03 Python
python:按行读入,排序然后输出的方法
Jul 20 Python
使用Python制作缩放自如的圣诞老人(圣诞树)
Dec 25 Python
Python 实现敏感目录扫描的示例代码
May 21 Python
Python将字典转换为XML的方法
Aug 01 #Python
Python Flask异步发送邮件实现方法解析
Aug 01 #Python
Python实现弹球小游戏
Aug 01 #Python
序列化Python对象的方法
Aug 01 #Python
Python 忽略文件名编码的方法
Aug 01 #Python
Python 如何展开嵌套的序列
Aug 01 #Python
Python 日期与时间转换的方法
Aug 01 #Python
You might like
php比较两个绝对时间的大小
2014/01/31 PHP
PHP_NETWORK_GETADDRESSES: GETADDRINFO FAILED问题解决办法
2014/05/04 PHP
php支付宝接口用法分析
2015/01/04 PHP
PHP连接access数据库
2015/03/27 PHP
Symfony2在Nginx下的配置方法图文教程
2016/02/04 PHP
PhpStorm 2020.3:新增开箱即用的PHP 8属性(推荐)
2020/10/30 PHP
按钮JS复制文本框和表格的代码
2011/04/01 Javascript
DWZ table的原生分页浅谈
2013/03/01 Javascript
jQuery setTimeout()函数使用方法
2013/04/07 Javascript
用原生JavaScript实现jQuery的$.getJSON的解决方法
2013/05/03 Javascript
javascript使用数组的push方法完成快速排序
2014/09/15 Javascript
SWFUpload多文件上传及文件个数限制的方法
2016/05/31 Javascript
jQuery弹出窗口打开链接的实现代码
2016/12/24 Javascript
javascript 秒表计时器实现代码
2017/03/09 Javascript
详解基于Vue2.0实现的移动端弹窗(Alert, Confirm, Toast)组件
2018/08/02 Javascript
JS前端知识点 运算符优先级,URL编码与解码,String,Math,arguments操作整理总结
2019/06/27 Javascript
Vue编程式跳转的实例代码详解
2019/07/10 Javascript
解决Layui中layer报错的问题
2019/09/03 Javascript
layui--js控制switch的切换方法
2019/09/03 Javascript
js实现全选和全不选
2020/07/28 Javascript
js实现简单的随机点名器
2020/09/17 Javascript
OpenLayers3实现地图鹰眼以及地图比例尺的添加
2020/09/25 Javascript
[16:56]教你分分钟做大人:司夜刺客
2014/10/30 DOTA
爬山算法简介和Python实现实例
2014/04/26 Python
在Python的Django框架中创建和使用模版
2015/07/15 Python
Python计算已经过去多少个周末的方法
2015/07/25 Python
Python向MySQL批量插数据的实例讲解
2018/03/31 Python
python文件操作的简单方法总结
2019/11/07 Python
使用python实现下载我们想听的歌曲,速度超快
2020/07/09 Python
python3.5的包存放的具体路径
2020/08/16 Python
Python CategoricalDtype自定义排序实现原理解析
2020/09/11 Python
澳大利亚100%丝绸多彩度假装商店:TheSwankStore
2019/09/04 全球购物
公安领导班子四风问题个人整改措施思想汇报
2014/10/09 职场文书
幼儿园教师心得体会范文
2016/01/21 职场文书
MySQL索引篇之千万级数据实战测试
2021/04/05 MySQL
Python OpenGL基本配置方式
2022/05/20 Python