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

问题

你想读写一个二进制数组的结构化数据到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读写二进制数组数据的资料请关注我们其它相关文章!

(0)

相关推荐

  • python 工具 字符串转numpy浮点数组的实现

    不同的数字之间使用 空格" ","$","*"等隔开,支持带小数点的字符串 NumArray=str2num(LineString,comment='#') 将字符串中的所有非Double类型的字符全部替换成空格 以'#'开头直至行尾的内容被清空 返回一维numpy.array数组 import numpy import scipy def str2num(LineString,comment='#'): from io import Strin

  • 在python3中实现查找数组中最接近与某值的元素操作

    我就废话不多说了,直接上代码吧! import datetime def find_close(arr, e): start_time = datetime.datetime.now() size = len(arr) idx = 0 val = abs(e - arr[idx]) for i in range(1, size): val1 = abs(e - arr[i]) if val1 < val: idx = i val = val1 use_time = datetime.dateti

  • python对数组进行排序,并输出排序后对应的索引值方式

    废话不多说,直接上代码吧! # -*- coding: cp936 -*- import numpy as np #一维数组排序 arr = [1, 3, 5, 2, 4, 6] arr = np.array(arr) print arr print np.sort(arr)#或print np.sort(arr,axis=None) print (np.argsort(arr)) # 正序输出索引,从小到大 print (np.argsort(-arr)) # 逆序输出索引,从大到小 输出结果

  • Python替换NumPy数组中大于某个值的所有元素实例

    我有一个2D(二维) NumPy数组,并希望用255.0替换大于或等于阈值T的所有值.据我所知,最基础的方法是: shape = arr.shape result = np.zeros(shape) for x in range(0, shape[0]): for y in range(0, shape[1]): if arr[x, y] >= T: result[x, y] = 255 有更简洁和pythonic的方式来做到这一点吗? 有没有更快(可能不那么简洁和/或不那么pythonic)的

  • python如何建立全零数组

    语句格式: numpy.zeros(shape, dtype=float, order='C') 参数说明: shape:整型或元素为整型的序列,表示生成的新数组的shape,如(2,3)或 2. dtype:生成数组的数据格式,如numpy.int8.默认为numpy.float64. order:{'C', 'F'}可选,是否将多维数据存储为C-或Fortran-contiguous(按行或按列)顺序. 返回值:ndarray,一个指定了shape, dtype, order的零数组. 示例

  • python 实现多维数组(array)排序

    关于多维数组如何复合排序 如数组: >>> import numpy as np >>> data = np.array([[2,2,5],[2,1,3],[1,2,3],[3,1,4]]) >>>> data array([[2, 2, 5], [2, 1, 3], [1, 2, 3], [3, 1, 4]]) 将数组先按照第一列升序,第二列升序,第三列升序的方式排序: >>> idex=np.lexsort([data[:,

  • python 实现在无序数组中找到中位数方法

    一.问题描述 1.求一个无序数组的中位数, (若数组是偶数,则中位数是指中间两个数字之和除以2,若数组是奇数,则中位数是指最中间位置.要求:不能使用排序,时间复杂度尽量低 2.例如: lists = [3, 2, 1, 4] , 中位数为 = (2+3)/2 = 2.5 lists = [3, 1, 2] , 中位数为 2 3.算法思想: 利用快速排序思想(但是并不是全部使用):任意挑选一个元素,以该元素为key, 划分数组为两个部分,如果左侧数组长度刚好为(n-1)/2, 那么key就为中位数

  • Python数组拼接np.concatenate实现过程

    在python中,用于数组拼接的主要来自numpy包,当然pandas包也可以完成. 而,numpy中可以使用append和concatenate函数: 1. 建立数组 # pandas有专门的连接方法 import numpy as np # np.size(a, 0) 行数 # np.size(a, 1) 列数 a = np.array([[1, 2],[3, 4],[5, 6]]) b = np.array([[11, 22],[33, 44],[55, 66]]) print(np.si

  • Python如何实现大型数组运算(使用NumPy)

    问题 你需要在大数据集(比如数组或网格)上面执行计算. 解决方案 涉及到数组的重量级运算操作,可以使用NumPy库.NumPy的一个主要特征是它会给Python提供一个数组对象,相比标准的Python列表而已更适合用来做数学运算.下面是一个简单的小例子,向你展示标准列表对象和NumPy数组对象之间的差别: >>> # Python lists >>> x = [1, 2, 3, 4] >>> y = [5, 6, 7, 8] >>>

  • Python 改变数组类型为uint8的实现

    第一次发博客,尝试一下 学习opencv3的时候,绘制hsv空间中的2d直方图,必须要将生成的hist数组的格式转换为uint8格式,否则应用cv2.imshow时图像不能显示! # data=np.array(hist,dtype='uint8') 补充知识:python中图片的float类型和uint8类型 在python图像处理过程中,遇到的RGB图像的值是处于0-255之间的,为了更好的处理图像,通常会将图像值转变到0-1之间 这个处理的过程就是图像的float类型转变为uint8类型过

  • python由已知数组快速生成新数组的方法

    需求描述 在利用numpy进行数据分析时,常有的一个需求是:根据已知的数组生成新数组.这个问题又可以分为两类: 根据筛选条件生成子数组: 根据变换条件生成新数组(新数组shape与原数组相同) 下面简单总结. 生成子数组 情况1 已知数组a,以及若干筛选条件conds,要求从数组a中生成一个子数组b. 解决办法:b=a[conds].比如b=a[a>0],b=a[(a>=1)|(a<=-2)], b=a[(a>=1)&(a<=3)] 实例:如下 # 实例1.1:已知数

随机推荐