164

I find particularly difficult reading binary file with Python. Can you give me a hand? I need to read this file, which in Fortran 90 is easily read by

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)

In detail, the file format is:

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. 

How can I read this with Python? I tried everything but it never worked. Is there any chance I might use a f90 program in python, reading this binary file and then save the data that I need to use?

Brian
  • 13,996
  • 19
  • 70
  • 94
  • 2
    Was this file written by a Fortran program? If so, how was it written, since Fortran, by default, adds additional data before each record it writes to file. You may need to take care with this when reading the data. – Chris Jan 03 '12 at 10:02
  • 1
    Please ignore my previous comment, the intergers 8 and 4*N are clearly this additional data. – Chris Jan 03 '12 at 10:43
  • 2
    Also, see answers to the question [reading binary file in python](http://stackoverflow.com/questions/1035340/reading-binary-file-in-python). – Chris Jan 03 '12 at 10:46
  • 1
    Numpy's `fromfile` function makes it easy to read binary files. I recommend it. – littleO Apr 17 '20 at 11:25
  • ...and always watch out for your endian-nesses, esp. when porting between different manufacturer's computers. – DragonLord May 26 '20 at 18:06

8 Answers8

225

Read the binary file content like this:

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

then "unpack" binary data using struct.unpack:

The start bytes: struct.unpack("iiiii", fileContent[:20])

The body: ignore the heading bytes and the trailing byte (= 24); The remaining part forms the body, to know the number of bytes in the body do an integer division by 4; The obtained quotient is multiplied by the string 'i' to create the correct format for the unpack method:

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

The end byte: struct.unpack("i", fileContent[-4:])

gecco
  • 17,969
  • 11
  • 51
  • 68
  • 1
    Can you please have look at this other post? http://stackoverflow.com/questions/8092469/reading-a-binary-file-in-python ... I am again to read another binary file, but in this case I don't know the byte structure in details. For example, I figured out that sometimes there is the integer 8. However, with IDL it is really simple to read this data. Can I do the same with python? – Brian Jan 04 '12 at 09:45
  • Please indicate (inside the other post, not here) why you are not happy with the posted answers and comments. Perhaps you should also update the question to provide more details... I'll have a look at it when it is updated. – gecco Jan 04 '12 at 09:59
  • See [this](http://stackoverflow.com/a/606199/635906) answer if you need to convert an unpacked char[] to a string. – PeterM May 20 '16 at 14:57
  • 1
    `import struct` – J W Feb 21 '17 at 10:17
  • Why divide by 4 ? – Sam Gomari Jul 16 '21 at 16:54
25

To read a binary file to a bytes object:

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

To create an int from bytes 0-3 of the data:

i = int.from_bytes(data[:4], byteorder='little', signed=False)

To unpack multiple ints from the data:

import struct
ints = struct.unpack('iiii', data[:16])
Eugene Yarmash
  • 142,882
  • 41
  • 325
  • 378
25

In general, I would recommend that you look into using Python's struct module for this. It's standard with Python, and it should be easy to translate your question's specification into a formatting string suitable for struct.unpack().

Do note that if there's "invisible" padding between/around the fields, you will need to figure that out and include it in the unpack() call, or you will read the wrong bits.

Reading the contents of the file in order to have something to unpack is pretty trivial:

import struct

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

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

This unpacks the first two fields, assuming they start at the very beginning of the file (no padding or extraneous data), and also assuming native byte-order (the @ symbol). The Is in the formatting string mean "unsigned integer, 32 bits".

unwind
  • 391,730
  • 64
  • 469
  • 606
  • ok, but I don't even know how to read the bytes of the file. From my question how can I read the file from bytes 5 to 8 and then convert the result to an integer? Sorry, but I'm new with Python. – Brian Jan 03 '12 at 10:39
  • What about *closing the file* ? It's a very bad practice leaving a file opened which can be easily avoided by using a `with` statement. The answer is useful but it can be misleading for people like me who are still learning how to handle files in Python – CamelCamelius Apr 13 '21 at 17:11
  • @CamelCamelius Note that we could think that the file would be automatically closed because the handle gets out of scope. But this is not always the case https://stackoverflow.com/questions/2404430/does-filehandle-get-closed-automatically-in-python-after-it-goes-out-of-scope – Fuujuhi Mar 02 '22 at 14:57
18

You could use numpy.fromfile, which can read data from both text and binary files. You would first construct a data type, which represents your file format, using numpy.dtype, and then read this type from file using numpy.fromfile.

Chris
  • 44,602
  • 16
  • 137
  • 156
  • 2
    Easy to miss this! Docs are a bit thin; see https://www.reddit.com/r/Python/comments/19q8nt/psa_consider_using_numpy_if_you_need_to_parse_a/ for some discussion – lost Mar 23 '17 at 10:55
1

I too found Python lacking when it comes to reading and writing binary files, so I wrote a small module (for Python 3.6+).

With binaryfile you'd do something like this (I'm guessing, since I don't know 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)

Which produces an output like this:

{
    '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
}

I used skip() to skip the additional data Fortran adds, but you may want to add a utility to handle Fortran records properly instead. If you do, a pull request would be welcome.

Fax
  • 384
  • 2
  • 9
0

If the data is array-like, I like to use numpy.memmap to load it.

Here's an example that loads 1000 samples from 64 channels, stored as two-byte integers.

import numpy as np
mm = np.memmap(filename, np.int16, 'r', shape=(1000, 64))

You can then slice the data along either axis:

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

All the usual formats are available, including C- and Fortran-order, various dtypes and endianness, etc.

Some advantages of this approach:

  • No data is loaded into memory until you actually use it (that's what a memmap is for).
  • More intuitive syntax (no need to generate a struct.unpack string consisting of 64000 character)
  • Data can be given any shape that makes sense for your application.

For non-array data (e.g., compiled code), heterogeneous formats ("10 chars, then 3 ints, then 5 floats, ..."), or similar, one of the other approaches given above probably makes more sense.

cxrodgers
  • 4,317
  • 2
  • 23
  • 29
-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)
ChrisGPT was on strike
  • 127,765
  • 105
  • 273
  • 257
  • Welcome to Stack Overflow. Code is a lot more helpful when it is accompanied by an explanation. Stack Overflow is about learning, not providing snippets to blindly copy and paste. Please [edit] your question and explain how it answers the specific question being asked. See [answer]. – ChrisGPT was on strike Mar 22 '22 at 23:32
  • And a couple of copyright notes: (a) There's generally no need to credit yourself on trivial code like this. (b) By writing this on Stack Overflow [you have licensed it under a Creative Commons license](https://stackoverflow.com/help/licensing). – ChrisGPT was on strike Mar 22 '22 at 23:33
-3
import pickle
f=open("filename.dat","rb")
try:
    while True:
        x=pickle.load(f)
        print x
except EOFError:
    pass
f.close()
Phil
  • 2,797
  • 1
  • 24
  • 30