Python comment écrire dans un fichier binaire?

128

J'ai une liste d'octets sous forme d'entiers, ce qui ressemble à quelque chose comme

[120, 3, 255, 0, 100]

Comment puis-je écrire cette liste dans un fichier sous forme binaire?

Cela fonctionnerait-il?

newFileBytes = [123, 3, 255, 0, 100]
# make file
newFile = open("filename.txt", "wb")
# write to file
newFile.write(newFileBytes)
Aaron Randonneur
la source
60
Vous demandez "Est-ce que cela fonctionnerait?". L'as tu essayé?
StephenTG
1
Devrait être TypeError: argument 1 must be string or buffer, not list.
anatoly techtonik

Réponses:

128

C'est exactement ce à quoi bytearraysert:

newFileByteArray = bytearray(newFileBytes)
newFile.write(newFileByteArray)

Si vous utilisez Python 3.x, vous pouvez utiliser à la bytesplace (et vous devriez probablement le faire, car cela signale mieux votre intention). Mais en Python 2.x, cela ne fonctionnera pas, car ce bytesn'est qu'un alias pour str. Comme d'habitude, montrer avec l'interprète interactif est plus facile que d'expliquer avec du texte, alors laissez-moi faire cela.

Python 3.x:

>>> bytearray(newFileBytes)
bytearray(b'{\x03\xff\x00d')
>>> bytes(newFileBytes)
b'{\x03\xff\x00d'

Python 2.x:

>>> bytearray(newFileBytes)
bytearray(b'{\x03\xff\x00d')
>>> bytes(newFileBytes)
'[123, 3, 255, 0, 100]'
Abarnert
la source
1
Bonne utilisation des types intégrés. Notez simplement que bytearray a été ajouté dans la version 2.6, si vous souhaitez prendre en charge les systèmes hérités, cela doit être évité.
Perkins
7
@Perkins: Bien sûr, et vous devriez éviter les expressions génératrices si vous devez travailler sur 2.3, soyez prudent avec les deux str.encodeet struct.packsi vous devez travailler sur 2.2. Mais 2.6 est sorti depuis 5 ans maintenant; les trois Ubuntu LTS toujours pris en charge, les trois versions d'OS X prises en charge, la version majeure précédente de CentOS / RHEL, etc., sont tous livrés avec lui intégré. Si vous avez besoin de prendre en charge 2.5 ou 2.1 ou 1.6 ou autre, vous savoir…
abarnert
4
Avec Python 2 sur Windows, j'ai trouvé que l'écriture d'un bytearrayconvertissait toujours \nen \r\n, ce qui le rend insatisfaisant pour les données binaires, si l'indicateur "b" n'est pas passé lors de l'ouverture du fichier.
feersum
6
@feersum: Bien sûr; c'est ce que signifie le mode binaire contre texte en 2.x. Peu importe le type d'où proviennent vos octets. (En 3.x, bien sûr, le mode binaire contre texte signifie que vous écrivez des octets contre unicode, et la \r\nfonctionnalité fait partie des options de retour à la ligne universelles pour le texte.)
abarnert
Je ne suis pas sûr que bytearray () soit un bon choix pour l'écriture de fichiers. Vous devrez limiter la taille aux blocs gérables. Sinon, une fois que la taille de vos fichiers sera trop élevée, vous manquerez de mémoire.
mckenzm
31

Utilisation struct.pack pour convertir les valeurs entières en octets binaires, puis écrivez les octets. Par exemple

newFile.write(struct.pack('5B', *newFileBytes))

Cependant, je ne donnerais jamais une .txtextension à un fichier binaire .

L'avantage de cette méthode est qu'elle fonctionne également pour d'autres types, par exemple si l'une des valeurs était supérieure à 255, vous pouvez utiliser '5i'le format à la place pour obtenir des entiers 32 bits complets.

Mark Ransom
la source
.txt convient si vous avez un moyen de savoir que les données que vous écrivez se situent toutes dans la plage ascii imprimable. Cependant, je pense que vous avez raison dans ce cas, car les données d'exemple incluent des caractères non imprimables.
Perkins
@Perkins Je n'ai pas fait l'hypothèse que les valeurs seraient même inférieures à 256 beaucoup moins dans la plage ASCII. Même s'ils le sont, les fichiers .txt doivent être réservés à ceux qui ont du sens pour un humain et qui ne s'appliquent jamais aux données binaires.
Mark Ransom
1
Vous avez raison, struct.pack est également la voie à suivre si vous allez écrire des données avec des valeurs supérieures à 255, car ni bytearray ni chr ne peuvent gérer des valeurs entières plus grandes.
Perkins
1
@MarkRansom: Eh bien, c'est toujours une bonne solution au problème plus général de "J'ai une liste d'entiers de taille arbitraire mais fixe, comment puis-je les écrire dans un fichier binaire?" et je peux voir des gens chercher cette question et trouver celle-ci…
abarnert
1
struct.pack est la meilleure réponse; c'est beaucoup plus flexible que de simplement créer un bytearray.
Seth
12

Pour convertir des entiers <256 en binaire, utilisez la chrfonction. Vous envisagez donc de faire ce qui suit.

newFileBytes=[123,3,255,0,100]
newfile=open(path,'wb')
newfile.write((''.join(chr(i) for i in newFileBytes)).encode('charmap'))
Perkins
la source
1
Vous devez signifier <128. Comme python3 se plaint: UnicodeEncodeError: le codec 'ascii' ne peut pas encoder le caractère '\ x89' en position 0: l'ordinal n'est pas dans l'intervalle (128)
élig
2
Non, je veux dire <256, mais l'encodage devrait être charmapplutôt que ascii, et fonctionne aussi bien en python2 qu'en python3. L' asciiencodage ne fonctionne qu'en python2.
Perkins
9

À partir de Python 3.2+, vous pouvez également accomplir cela en utilisant la to_bytesméthode native int:

newFileBytes = [123, 3, 255, 0, 100]
# make file
newFile = open("filename.txt", "wb")
# write to file
for byte in newFileBytes:
    newFile.write(byte.to_bytes(1, byteorder='big'))

C'est-à-dire que chaque appel à to_bytesdans ce cas crée une chaîne de longueur 1, avec ses caractères disposés dans l'ordre big-endian (ce qui est trivial pour les chaînes de longueur 1), qui représente la valeur entière byte. Vous pouvez également raccourcir les deux dernières lignes en une seule:

newFile.write(''.join([byte.to_bytes(1, byteorder='big') for byte in newFileBytes]))
CrêpeChèvre
la source
8

Vous pouvez utiliser l'exemple de code suivant à l'aide de la syntaxe Python 3:

from struct import pack
with open("foo.bin", "wb") as file:
  file.write(pack("<IIIII", *bytearray([120, 3, 255, 0, 100])))

Voici la coque one-liner:

python -c $'from struct import pack\nwith open("foo.bin", "wb") as file: file.write(pack("<IIIII", *bytearray([120, 3, 255, 0, 100])))'
Kenorb
la source
0

Utilisez pickle, comme ceci: import pickle

Votre code ressemblerait à ceci:

import pickle
mybytes = [120, 3, 255, 0, 100]
with open("bytesfile", "wb") as mypicklefile:
    pickle.dump(mybytes, mypicklefile)

Pour relire les données, utilisez la méthode pickle.load

Raymond Mlambo
la source
3
Cela ne produit pas un fichier binaire de 5 octets de longueur, dont le seul contenu est 120, 3, 255, 0, 100. Dans un système fermé, cela peut être acceptable.
parvus