Ler um arquivo binário com python


104

Acho particularmente difícil ler arquivos binários com Python. Você pode me dar a mão? Eu preciso ler este arquivo, que em Fortran 90 é facilmente lido 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)

Em detalhes, o formato do arquivo é:

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. 

Como posso ler isso com Python? Tentei de tudo, mas nunca funcionou. Existe alguma chance de eu usar um programa f90 em python, lendo este arquivo binário e salvar os dados que preciso usar?


1
Este arquivo foi escrito por um programa Fortran? Se sim, como foi escrito, já que o Fortran, por padrão, adiciona dados adicionais antes de cada registro que grava no arquivo. Pode ser necessário ter cuidado com isso ao ler os dados.
Chris

1
Por favor, ignore meu comentário anterior, os inteiros 8 e 4 * N são claramente esses dados adicionais.
Chris

2
Além disso, consulte as respostas à pergunta sobre a leitura de arquivo binário em python .
Chris

A fromfilefunção do Numpy facilita a leitura de arquivos binários. Eu recomendo.
LittleO

... e sempre esteja atento às suas endian-nes, esp. ao transferir entre computadores de fabricantes diferentes.
DragonLord

Respostas:


155

Leia o conteúdo do arquivo binário assim:

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

em seguida, "descompacte" os dados binários usando struct.unpack :

Os bytes iniciais: struct.unpack("iiiii", fileContent[:20])

O corpo: ignore os bytes do cabeçalho e o byte final (= 24); A parte restante forma o corpo, para saber o número de bytes no corpo faça uma divisão inteira por 4; O quociente obtido é multiplicado pela string 'i'para criar o formato correto para o método de descompactação:

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

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


Você pode dar uma olhada neste outro post? stackoverflow.com/questions/8092469/… ... Devo ler novamente outro arquivo binário, mas neste caso não conheço a estrutura de bytes em detalhes. Por exemplo, descobri que às vezes existe o número inteiro 8. No entanto, com IDL é realmente simples ler esses dados. Posso fazer o mesmo com o python?
Brian

Indique (dentro da outra postagem, não aqui) porque você não está satisfeito com as respostas e comentários postados. Talvez você também deva atualizar a pergunta para fornecer mais detalhes ... Eu darei uma olhada nela quando for atualizada.
gecco

Veja esta resposta se precisar converter um char descompactado [] em uma string.
PeterM de

import struct
JW de

23

Em geral, eu recomendo que você use o módulo struct do Python para isso. É padrão com Python e deve ser fácil traduzir a especificação de sua pergunta em uma string de formatação adequada para struct.unpack().

Observe que, se houver um preenchimento "invisível" entre / ao redor dos campos, você precisará descobrir isso e incluí-lo na unpack()chamada, ou lerá os bits errados.

Ler o conteúdo do arquivo para ter algo para descompactar é bastante trivial:

import struct

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

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

Isso descompacta os dois primeiros campos, supondo que eles comecem no início do arquivo (sem preenchimento ou dados estranhos) e também assumindo a ordem de byte nativa (o @símbolo). Os Is na string de formatação significam "inteiro sem sinal, 32 bits".


ok, mas nem sei ler os bytes do arquivo. Da minha pergunta, como posso ler o arquivo dos bytes 5 a 8 e, em seguida, converter o resultado em um inteiro? Desculpe, mas sou novo em Python.
Brian


11

Para ler um arquivo binário para um bytesobjeto:

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

Para criar um intdos bytes 0-3 dos dados:

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

Para descompactar vários ints dos dados:

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

0

Eu também achei Python deficiente quando se trata de ler e escrever arquivos binários, então escrevi um pequeno módulo (para Python 3.6+).

Com binaryfile você faria algo assim (estou supondo, já que não conheço 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)

O que produz uma saída 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
}

Usei skip () para pular os dados adicionais que o Fortran adiciona, mas você pode querer adicionar um utilitário para lidar com os registros Fortran corretamente. Se você fizer isso, uma solicitação de pull será bem-vinda.


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

6
Provavelmente vale apenas uma pequena explicação de por que isso é melhor (ou pelo menos tão bom quanto) outras respostas.
Phil

2
você testou e verificou que isso funciona com o binário gerado pelo fortran?
agente de

1
E também explique o que ele faz ... O que é picles? O que pickle.loadcarrega? Ele carrega um fluxo Fortran, arquivos diretos ou sequenciais? Eles são diferentes e não compatíveis.
Vladimir F
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.