在Python中读取二进制文件

4
我编写了一个Python脚本来创建整数的二进制文件。
import struct  
pos = [7623, 3015, 3231, 3829]  
inh = open('test.bin', 'wb')  
for e in pos:  
    inh.write(struct.pack('i', e))  
inh.close()

它之前运作良好,然后我尝试使用以下代码读取'test.bin'文件。

import struct  
inh = open('test.bin', 'rb')  
for rec in inh:  
    pos = struct.unpack('i', rec)  
    print pos  
inh.close()

但是它失败了,并显示出一个错误信息:

Traceback (most recent call last):   
   File "readbinary.py", line 10, in <module>  
   pos = struct.unpack('i', rec)  
   File "/usr/lib/python2.5/struct.py", line 87, in unpack  
   return o.unpack(s)  
struct.error: unpack requires a string argument of length 4

我想知道如何使用struct.unpack读取这些文件。
非常感谢,Vipin

6个回答

8

for rec in inh: 一次只读取一行,对于二进制文件不是你想要的方式。改用每次读取4个字节(使用while循环和inh.read(4))或者将所有内容一次性读入内存中,然后解压连续的4字节切片。第二种方法最简单、最实用,只要涉及的数据量不是太大即可。

import struct
with open('test.bin', 'rb') as inh:
    indata = inh.read()
for i in range(0, len(data), 4):
    pos = struct.unpack('i', data[i:i+4])  
    print(pos)  

如果你担心可能会有巨量的数据(这些数据所需的内存超过了你可用的内存),那么一个简单的生成器提供了一种优雅的替代方案:

import struct
def by4(f):
    rec = 'x'  # placeholder for the `while`
    while rec:
        rec = f.read(4)
        if rec: yield rec           
with open('test.bin', 'rb') as inh:
    for rec in by4(inh):
        pos = struct.unpack('i', rec)  
        print(pos)  

这种方法的一个重要优势是,可以轻松地调整by4生成器(同时保持规范:每次返回二进制文件的数据4个字节),以使用不同的缓冲实现策略,一直到第一种方法(读取所有内容然后分配)可以看作是“无限缓冲”并进行编码。
def by4(f):
    data = inf.read()
    for i in range(0, len(data), 4):
        yield data[i:i+4]

通过将 I/O 层(封装在生成器内部)独立于“应用逻辑”(对这个由 4 字节块组成的数据流要进行什么操作)而保持不变。


2
by4的第一个版本一旦“rec = f.read(4)”返回零就会挂起。while循环没有退出。 - Mark Tolonen
另一种选择是使用import functools; reader= functools.partial(f.read, 4); for rec in iter(reader, ''):结构。否则,可以避免使用functools及其加速:for rec in iter(lambda: f.read(4), '') - tzot

5

我认为"for rec in inh"应该读作'lines'而不是'bytes'。你需要的是:

while True:
    rec = inh.read(4) # Or inh.read(struct.calcsize('i'))
    if len(rec) != 4:
        break
    (pos,) = struct.unpack('i', rec)
    print pos

或者如其他人所提到的:

while True:
    try:
        (pos,) = struct.unpack_from('i', inh)
    except (some_exception...):
        break

1

这个函数从文件中读取所有字节

def read_binary_file(filename):
try:
    f = open(filename, 'rb')
    n = os.path.getsize(filename)
    data = array.array('B')
    data.read(f, n)
    f.close()
    fsize = data.__len__()
    return (fsize, data)

except IOError:
    return (-1, [])

# somewhere in your code
t = read_binary_file(FILENAME)
fsize = t[0]

if (fsize > 0):
    data = t[1]
    # work with data
else:
    print 'Error reading file'

1

检查打包整数的大小:

>>> pos
[7623, 3015, 3231, 3829]
>>> [struct.pack('i',e) for e in pos]
['\xc7\x1d\x00\x00', '\xc7\x0b\x00\x00', '\x9f\x0c\x00\x00', '\xf5\x0e\x00\x00']

我们看到4字节的字符串,这意味着每次读取应该是4个字节:

>>> inh=open('test.bin','rb')
>>> b1=inh.read(4)
>>> b1
'\xc7\x1d\x00\x00'
>>> struct.unpack('i',b1)
(7623,)
>>> 

这是原始的int!将其扩展为一个读取循环留作练习。


1

如果你想的话,也可以使用array

import array  
pos = array.array('i', [7623, 3015, 3231, 3829]) 
inh = open('test.bin', 'wb')  
pos.write(inh)
inh.close()

然后使用 array.array.fromfilefromstring 将其读取回来。


0

你的迭代器没有一次读取4个字节,所以我想它可能会感到困惑。正如SilentGhost所提到的,最好使用unpack_from()。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接