Lire un fichier binaire avec python

104

Je trouve particulièrement difficile la lecture de fichier binaire avec Python. Peux-tu me donner un coup de main? J'ai besoin de lire ce fichier, qui dans Fortran 90 est facilement lu par

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 détail, le format de fichier est:

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. 

Comment puis-je lire cela avec Python? J'ai tout essayé mais cela n'a jamais fonctionné. Y a-t-il une chance que je puisse utiliser un programme f90 en python, lire ce fichier binaire puis enregistrer les données dont j'ai besoin?

Brian
la source
1
Ce fichier a-t-il été écrit par un programme Fortran? Si tel est le cas, comment a-t-il été écrit, puisque Fortran, par défaut, ajoute des données supplémentaires avant chaque enregistrement qu'il écrit dans le fichier. Vous devrez peut-être faire attention lors de la lecture des données.
Chris
1
Veuillez ignorer mon commentaire précédent, les intergers 8 et 4 * N sont clairement ces données supplémentaires.
Chris
2
Consultez également les réponses à la question sur la lecture du fichier binaire en python .
Chris
La fromfilefonction de Numpy facilite la lecture des fichiers binaires. Je le recommande.
littleO
... et faites toujours attention à vos endian-nesses, esp. lors du portage entre les ordinateurs de différents fabricants.
DragonLord

Réponses:

155

Lisez le contenu du fichier binaire comme ceci:

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

puis "décompresser" les données binaires en utilisant struct.unpack :

Les octets de début: struct.unpack("iiiii", fileContent[:20])

Le corps: ignorez les octets d'en-tête et l'octet de fin (= 24); La partie restante forme le corps, pour connaître le nombre d'octets dans le corps faire une division entière par 4; Le quotient obtenu est multiplié par la chaîne 'i'pour créer le format correct pour la méthode de décompression:

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

L'octet de fin: struct.unpack("i", fileContent[-4:])

gecco
la source
Pouvez-vous jeter un œil à cet autre article? stackoverflow.com/questions/8092469/ ... ... Je suis à nouveau pour lire un autre fichier binaire, mais dans ce cas, je ne connais pas la structure d'octets en détail. Par exemple, j'ai compris que parfois il y a l'entier 8. Cependant, avec IDL, il est vraiment simple de lire ces données. Puis-je faire la même chose avec python?
Brian
Veuillez indiquer (dans l'autre article, pas ici) pourquoi vous n'êtes pas satisfait des réponses et des commentaires publiés. Peut-être devriez-vous également mettre à jour la question pour fournir plus de détails ... Je l'examinerai lorsqu'elle sera mise à jour.
gecco
Consultez cette réponse si vous avez besoin de convertir un char [] décompressé en chaîne.
PeterM
import struct
JW
23

En général, je vous recommande d'utiliser le module struct de Python pour cela. C'est standard avec Python, et il devrait être facile de traduire la spécification de votre question dans une chaîne de formatage adaptée à struct.unpack().

Notez que s'il y a un remplissage "invisible" entre / autour des champs, vous devrez le comprendre et l'inclure dans l' unpack()appel, ou vous lirez les mauvais bits.

Lire le contenu du fichier pour avoir quelque chose à décompresser est assez trivial:

import struct

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

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

Cela décompresse les deux premiers champs, en supposant qu'ils commencent au tout début du fichier (pas de remplissage ou de données superflues), et en supposant également l'ordre des octets natif (le @symbole). Le Is dans la chaîne de formatage signifie "entier non signé, 32 bits".

se détendre
la source
ok, mais je ne sais même pas comment lire les octets du fichier. D'après ma question, comment puis-je lire le fichier des octets 5 à 8, puis convertir le résultat en entier? Désolé, mais je suis nouveau avec Python.
Brian
14

Vous pouvez utiliser numpy.fromfile, qui peut lire des données à partir de fichiers texte et binaires. Vous devez d'abord construire un type de données, qui représente votre format de fichier, en utilisant numpy.dtype, puis lire ce type à partir du fichier en utilisant numpy.fromfile.

Chris
la source
2
Facile de rater ça! Les documents sont un peu minces; voir reddit.com/r/Python/comments/19q8nt/… pour une discussion
perdu
11

Pour lire un fichier binaire dans un bytesobjet:

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

Pour créer un à intpartir des octets 0-3 des données:

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

Pour décompresser plusieurs ints des données:

import struct
ints = struct.unpack('iiii', data[:16])
Eugène Yarmash
la source
0

J'ai moi aussi trouvé que Python manquait quand il s'agissait de lire et d'écrire des fichiers binaires, j'ai donc écrit un petit module (pour Python 3.6+).

Avec binaryfile, vous feriez quelque chose comme ça (je suppose, puisque je ne connais pas 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)

Ce qui produit une sortie comme celle-ci:

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

J'ai utilisé skip () pour ignorer les données supplémentaires ajoutées par Fortran, mais vous voudrez peut-être ajouter un utilitaire pour gérer correctement les enregistrements Fortran. Si vous le faites, une demande d'extraction serait la bienvenue.

Fax
la source
-2
import pickle
f=open("filename.dat","rb")
try:
    while True:
        x=pickle.load(f)
        print x
except EOFError:
    pass
f.close()
Eeshitri
la source
7
Cela vaut probablement juste une petite explication sur les raisons pour lesquelles c'est meilleur (ou au moins aussi bon que) les autres réponses.
Phil
2
avez-vous testé et vérifié que cela fonctionne avec le binaire généré par fortran?
agentp
1
Et aussi expliquer ce que ça fait ... Qu'est-ce que le cornichon? Qu'est-ce que la pickle.loadcharge? Charge-t-il un flux Fortran, des fichiers directs ou séquentiels? Ils sont différents et non compatibles.
Vladimir F