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学习笔记(一)(基础入门之环境搭建)
Jun 05 Python
python使用nntp读取新闻组内容的方法
May 08 Python
通过源码分析Python中的切片赋值
May 08 Python
使用python实现抓取腾讯视频所有电影的爬虫
Apr 15 Python
Python 操作mysql数据库查询之fetchone(), fetchmany(), fetchall()用法示例
Oct 17 Python
python 两个数据库postgresql对比
Oct 21 Python
python通过matplotlib生成复合饼图
Feb 06 Python
Python关于反射的实例代码分享
Feb 20 Python
Win10下配置tensorflow-gpu的详细教程(无VS2015/2017)
Jul 14 Python
Python3+RIDE+RobotFramework自动化测试框架搭建过程详解
Sep 23 Python
Python基于内置函数type创建新类型
Oct 22 Python
Python连接Postgres/Mysql/Mongo数据库基本操作大全
Jun 29 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
理解PHP5中static和const关键字的区别
2007/03/19 PHP
Linux下php5.4启动脚本
2014/08/03 PHP
php错误日志简单配置方法
2016/07/11 PHP
Yii2 队列 shmilyzxt/yii2-queue 简单概述
2017/08/02 PHP
Laravel 5.5基于内置的Auth模块实现前后台登陆详解
2017/12/21 PHP
PHP封装curl的调用接口及常用函数详解
2018/05/31 PHP
safari下载文件自动加了html后缀问题
2018/11/09 PHP
js实现日期级联效果
2014/01/23 Javascript
js 获取时间间隔实现代码
2014/05/12 Javascript
JavaScript中的依赖注入详解
2015/03/18 Javascript
浅析Ajax语法
2016/12/05 Javascript
微信小程序之仿微信漂流瓶实例
2016/12/09 Javascript
利用PM2部署node.js项目的方法教程
2017/05/10 Javascript
jquery插件开发之选项卡制作详解
2017/08/30 jQuery
Node.js学习教程之HTTP/2服务器推送【译】
2017/10/31 Javascript
babel之配置文件.babelrc入门详解
2018/02/22 Javascript
layer弹出层倒计时关闭的实现方法
2019/09/27 Javascript
使用原生JS实现火锅点餐小程序(面向对象思想)
2019/12/10 Javascript
Vue 中使用 typescript的方法详解
2020/02/17 Javascript
[00:37]DOTA2上海特级锦标赛 Secert 战队宣传片
2016/03/03 DOTA
在Windows服务器下用Apache和mod_wsgi配置Python应用的教程
2015/05/06 Python
学习Python3 Dlib19.7进行人脸面部识别
2018/01/24 Python
运用Python的webbrowser实现定时打开特定网页
2019/02/21 Python
python与C、C++混编的四种方式(小结)
2019/07/15 Python
解决pycharm最左侧Tool Buttons显示不全的问题
2019/12/17 Python
Python任务调度模块APScheduler使用
2020/04/15 Python
matlab 计算灰度图像的一阶矩,二阶矩,三阶矩实例
2020/04/22 Python
HTML5自定义属性前缀data-及dataset的使用方法(html5 新特性)
2017/08/24 HTML / CSS
巴西最大的家具及装饰用品店:Mobly
2017/10/11 全球购物
外贸英语专业求职信范文
2013/12/25 职场文书
给同事的道歉信
2014/01/11 职场文书
12.4全国法制宣传日活动方案
2014/11/02 职场文书
2016年春节慰问信息
2015/03/25 职场文书
2015七夕情人节宣传语
2015/07/14 职场文书
四年级作文之植物
2019/09/20 职场文书
Nginx Rewrite使用场景及配置方法解析
2021/04/01 Servers