使用Python读取二进制文件

165

我发现使用Python读取二进制文件特别困难。你能帮我一下吗? 我需要读取这个文件,而在Fortran 90中很容易读取它。

int*4 n_particles, n_groups
real*4 group_id(n_particles)
read (*) n_particles, n_groups
read (*) (group_id(j),j=1,n_particles)

具体而言,文件格式如下:

Bytes 1-4 -- The integer 8.
Bytes 5-8 -- The number of particles, N.
Bytes 9-12 -- The number of groups.
Bytes 13-16 -- The integer 8.
Bytes 17-20 -- The integer 4*N.
Next many bytes -- The group ID numbers for all the particles.
Last 4 bytes -- The integer 4*N. 

我该如何使用Python读取这个文件?我尝试了很多方法,但都没有成功。是否有可能在Python中使用f90程序读取这个二进制文件并保存我需要使用的数据?


2
这个文件是由Fortran程序编写的吗?如果是,它是如何编写的,因为Fortran默认在每个记录写入文件之前添加额外的数据。在读取数据时,您可能需要注意这一点。 - Chris
1
请忽略我的先前评论,整数8和4*N显然是这个额外数据。 - Chris
2
此外,还可以查看有关在Python中读取二进制文件的问题的答案reading binary file in python - Chris
1
Numpy的fromfile函数使读取二进制文件变得轻而易举。我建议使用它。 - littleO
...并且在移植到不同制造商的计算机之间时,始终注意你的字节序,特别是大小端问题。 - DragonLord
8个回答

229

像这样读取二进制文件内容:

with open(fileName, mode='rb') as file: # b is important -> binary
    fileContent = file.read()

使用struct.unpack来"解压"二进制数据:

起始字节:struct.unpack("iiiii", fileContent[:20])

主体部分:忽略头部和尾部(= 24)字节;剩余部分为主体,要知道主体中的字节数,请用4进行整数除法;得到的商乘以字符串'i'形成正确的格式以供解压方法使用:

struct.unpack("i" * ((len(fileContent) -24) // 4), fileContent[20:-4])

最后一个字节: struct.unpack("i", fileContent[-4:])


1
你能否看一下这篇帖子?https://dev59.com/aF3Ua4cB1Zd3GeqP9iGe ... 我又要读取另一个二进制文件,但是这次我不知道字节结构的详细信息。例如,我发现有时候会出现整数8。然而,使用IDL读取这些数据非常简单。我能用Python做到同样的事情吗? - Brian
请在其他帖子中指出您对已发布的答案和评论不满意的原因。也许您应该更新问题以提供更多细节......当问题更新后,我会查看它。 - gecco
如果需要将未打包的char []转换为字符串,请参见答案。 - PeterM
1
import struct - J W
为什么要除以4? - Sam Gomari

25

一般而言,我建议您查看使用Python的struct模块。它是Python标准库中的一部分,并且应该很容易地将您问题的规范转换为适合struct.unpack()的格式化字符串。

请注意,如果字段之间/周围存在“隐藏”的填充,则需要找出并将其包含在unpack()调用中,否则将读取错误的位。

按顺序读取文件内容以便进行拆包是非常简单的:

import struct

data = open("from_fortran.bin", "rb").read()

(eight, N) = struct.unpack("@II", data)

这将解包文件的前两个字段,假设它们从文件的开头开始(没有填充或多余数据),并且假设本机字节顺序(@符号)。 格式化字符串中的I表示“无符号整数,32位”。


好的,但我甚至不知道如何读取文件的字节。从我的问题中,我该如何从第5到第8个字节读取文件,然后将结果转换为整数?抱歉,但我对Python还很陌生。 - Brian
1
关于关闭文件怎么处理?通过使用with语句可以很容易地避免留下打开的文件,这是一个非常糟糕的做法。这个答案是有用的,但对于像我这样正在学习如何在Python中处理文件的人来说可能会产生误导。 - CamelCamelius
请注意,我们可能认为文件会自动关闭,因为句柄超出范围。但这并不总是如此。 https://dev59.com/WHE95IYBdhLWcg3wOLQ1 - Fuujuhi

25

读取二进制文件到 bytes 对象:

from pathlib import Path
data = Path('/path/to/file').read_bytes()  # Python 3.5+

要从数据的0-3字节创建一个int

i = int.from_bytes(data[:4], byteorder='little', signed=False)
为了从数据中解压多个 int
import struct
ints = struct.unpack('iiii', data[:16])
  • pathlib:Python标准库中的一个模块,提供了一种面向对象的处理文件系统路径的方式。
  • int.from_bytes():Python内置函数之一,用于将一组字节转换为整数。
  • struct:Python标准库中的模块,用于解析和打包二进制数据。

18
您可以使用numpy.fromfile,它可以从文本和二进制文件中读取数据。您首先需要构造一个数据类型,表示您的文件格式,使用numpy.dtype,然后使用numpy.fromfile从文件中读取此类型。

2
很容易忽略!文档有点薄;请参阅 https://www.reddit.com/r/Python/comments/19q8nt/psa_consider_using_numpy_if_you_need_to_parse_a/ 以获取一些讨论。 - lost

1

在处理二进制文件的读写时,我发现Python的能力有限,因此我编写了一个小模块(适用于Python 3.6+)。

使用binaryfile,您可以像这样操作(我猜测,因为我不了解Fortran):

import binaryfile

def particle_file(f):
    f.array('group_ids')  # Declare group_ids to be an array (so we can use it in a loop)
    f.skip(4)  # Bytes 1-4
    num_particles = f.count('num_particles', 'group_ids', 4)  # Bytes 5-8
    f.int('num_groups', 4)  # Bytes 9-12
    f.skip(8)  # Bytes 13-20
    for i in range(num_particles):
        f.struct('group_ids', '>f')  # 4 bytes x num_particles
    f.skip(4)

with open('myfile.bin', 'rb') as fh:
    result = binaryfile.read(fh, particle_file)
print(result)

这将产生以下输出:

{
    'group_ids': [(1.0,), (0.0,), (2.0,), (0.0,), (1.0,)],
    '__skipped': [b'\x00\x00\x00\x08', b'\x00\x00\x00\x08\x00\x00\x00\x14', b'\x00\x00\x00\x14'],
    'num_particles': 5,
    'num_groups': 3
}

我使用了skip()来跳过Fortran添加的额外数据,但是您可能需要添加一个工具来正确处理Fortran记录。如果您这样做,欢迎您提交一个pull request。


0
如果数据类似于数组,我喜欢使用 numpy.memmap 来加载它。
以下是一个例子,从64通道加载1000个样本,存储为两个字节的整数。
import numpy as np
mm = np.memmap(filename, np.int16, 'r', shape=(1000, 64))

然后您可以沿着任一轴切片数据:

mm[5, :] # sample 5, all channels
mm[:, 5] # all samples, channel 5

所有常见格式都可用,包括C和Fortran顺序、各种数据类型和字节序等。

该方法的一些优点:

  • 在实际使用之前,不会将任何数据加载到内存中(这就是memmap的作用)。
  • 更直观的语法(无需生成由64000个字符组成的struct.unpack字符串)
  • 数据可以被赋予任何对您的应用程序有意义的形状。

对于非数组数据(例如已编译的代码)、异构格式(“10个字符,然后3个整数,然后5个浮点数,…”)或类似情况,上述其他方法中的一种可能更有意义。


-2
#!/usr/bin/python

import array
data = array.array('f')
f = open('c:\\code\\c_code\\no1.dat', 'rb')
data.fromfile(f, 5)
print(data)

1
欢迎来到 Stack Overflow。代码如果附带解释会更有帮助。Stack Overflow 是关于学习的,而不是提供可以盲目复制粘贴的代码片段。请[编辑]您的问题并解释它如何回答所提出的具体问题。请参见[答案]。 - Chris
1
还有一些版权注意事项:(a) 对于这种琐碎的代码,通常没有必要在其中署名。 (b) 通过在 Stack Overflow 上编写此代码,您已将其许可为 Creative Commons 许可证。 - Chris

-3
import pickle
f=open("filename.dat","rb")
try:
    while True:
        x=pickle.load(f)
        print x
except EOFError:
    pass
f.close()

9
也许值得简单解释一下为什么这个答案比其他答案更好(或至少和其他答案一样好)。 - Phil
3
你测试并验证过这个方法可以与由Fortran生成的二进制文件一起使用吗? - agentp
4
并且解释一下它是做什么的... 什么是pickle?pickle.load加载什么?它是否加载Fortran流、直接或顺序文件?它们是不同的,不兼容。 - Vladimir F Героям слава
Pickle二进制文件包含数据的信息。你可以自己测试一下。 - Byron

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