6.11 读写二进制数组数据

pack() 和 unpack() 方法被用来打包和解包数据。

iter

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

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

unpack()

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

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

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

numpy

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

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