Leyendo un archivo binario con Python

104

Encuentro particularmente difícil leer archivos binarios con Python. ¿Puedes darme una mano? Necesito leer este archivo, que en Fortran 90 es fácilmente leído por

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)

En detalle, el formato de archivo es:

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. 

¿Cómo puedo leer esto con Python? Intenté todo pero nunca funcionó. ¿Existe alguna posibilidad de que pueda usar un programa f90 en Python, leer este archivo binario y luego guardar los datos que necesito usar?

Brian
fuente
1
¿Este archivo fue escrito por un programa de Fortran? Si es así, ¿cómo se escribió? Ya que Fortran, por defecto, agrega datos adicionales antes de cada registro que escribe en el archivo. Es posible que deba tener cuidado con esto al leer los datos.
Chris
1
Por favor ignore mi comentario anterior, los números enteros 8 y 4 * N son claramente estos datos adicionales.
Chris
2
Además, vea las respuestas a la pregunta sobre la lectura de un archivo binario en python .
Chris
La fromfilefunción de Numpy facilita la lectura de archivos binarios. Lo recomiendo.
littleO
... y siempre ten cuidado con tus endian-nesses, esp. al migrar entre computadoras de diferentes fabricantes.
DragonLord

Respuestas:

155

Lea el contenido del archivo binario como este:

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

luego "descomprime" los datos binarios usando struct.unpack :

Los bytes de inicio: struct.unpack("iiiii", fileContent[:20])

El cuerpo: ignore los bytes de encabezado y el byte final (= 24); La parte restante forma el cuerpo, para saber el número de bytes en el cuerpo haz una división entera entre 4; El cociente obtenido se multiplica por la cadena 'i'para crear el formato correcto para el método de descomprimir:

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

El byte final: struct.unpack("i", fileContent[-4:])

gecco
fuente
¿Puedes echar un vistazo a esta otra publicación? stackoverflow.com/questions/8092469/… ... De nuevo voy a leer otro archivo binario, pero en este caso no conozco la estructura de bytes en detalles. Por ejemplo, descubrí que a veces existe el entero 8. Sin embargo, con IDL es realmente sencillo leer estos datos. ¿Puedo hacer lo mismo con Python?
Brian
Indique (dentro de la otra publicación, no aquí) por qué no está satisfecho con las respuestas y comentarios publicados. Quizás también debería actualizar la pregunta para proporcionar más detalles ... La echaré un vistazo cuando se actualice.
gecco
Vea esta respuesta si necesita convertir un char [] descomprimido en una cadena.
PeterM
import struct
JW
23

En general, le recomendaría que considere usar el módulo struct de Python para esto. Es estándar en Python, y debería ser fácil traducir la especificación de su pregunta a una cadena de formato adecuada para struct.unpack().

Tenga en cuenta que si hay un relleno "invisible" entre / alrededor de los campos, tendrá que averiguarlo e incluirlo en la unpack()llamada, o leerá los bits incorrectos.

Leer el contenido del archivo para tener algo que descomprimir es bastante trivial:

import struct

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

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

Esto descomprime los dos primeros campos, asumiendo que comienzan desde el principio del archivo (sin relleno o datos extraños), y también asumiendo el orden de bytes nativo (el @símbolo). La Is en la cadena de formato significa "entero sin signo, 32 bits".

relajarse
fuente
ok, pero ni siquiera sé cómo leer los bytes del archivo. De mi pregunta, ¿cómo puedo leer el archivo de los bytes 5 a 8 y luego convertir el resultado a un número entero? Lo siento, pero soy nuevo en Python.
Brian
14

Puede usar numpy.fromfile, que puede leer datos de archivos de texto y binarios. Primero debe construir un tipo de datos, que representa su formato de archivo, usando numpy.dtype, y luego leer este tipo desde el archivo usando numpy.fromfile.

Chris
fuente
2
¡Es fácil perderse esto! Los documentos son un poco delgados; ver reddit.com/r/Python/comments/19q8nt/… para un poco de discusión
perdido el
11

Para leer un archivo binario en un bytesobjeto:

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

Para crear un intdesde los bytes 0-3 de los datos:

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

Para descomprimir varios correos electrónicos intde los datos:

import struct
ints = struct.unpack('iiii', data[:16])
Eugene Yarmash
fuente
0

Yo también encontré que Python carece de lectura y escritura de archivos binarios, así que escribí un pequeño módulo (para Python 3.6+).

Con binaryfile harías algo como esto (supongo, ya que no conozco a 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)

Lo que produce una salida como esta:

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

Usé skip () para omitir los datos adicionales que agrega Fortran, pero es posible que desee agregar una utilidad para manejar los registros de Fortran correctamente. Si lo hace, sería bienvenida una solicitud de extracción.

Fax
fuente
-2
import pickle
f=open("filename.dat","rb")
try:
    while True:
        x=pickle.load(f)
        print x
except EOFError:
    pass
f.close()
Eeshitri
fuente
6
Probablemente valga la pena una pequeña explicación de por qué esto es mejor (o al menos tan bueno) que otras respuestas.
Phil
2
¿Ha probado y verificado que esto funciona con el binario generado por fortran?
agentp
1
Y también explica qué hace ... ¿Qué es el pepinillo? ¿Qué pickle.loadcarga? ¿Carga un flujo de Fortran, archivos directos o secuenciales? Son diferentes y no compatibles.
Vladimir F