En Python, comment lire dans un fichier binaire et boucler chaque octet de ce fichier?
Python 2.4 et versions antérieures
f = open("myfile", "rb")
try:
byte = f.read(1)
while byte != "":
# Do stuff with byte.
byte = f.read(1)
finally:
f.close()
Python 2.5-2.7
with open("myfile", "rb") as f:
byte = f.read(1)
while byte != "":
# Do stuff with byte.
byte = f.read(1)
Notez que l'instruction with n'est pas disponible dans les versions de Python inférieures à 2.5. Pour l'utiliser dans la version 2.5, vous devez l'importer:
from __future__ import with_statement
En 2.6 cela n'est pas nécessaire.
Python
Dans Python 3, c'est un peu différent. Nous n'obtiendrons plus les caractères bruts du flux en mode octet, mais des objets octets; nous devons donc modifier la condition:
with open("myfile", "rb") as f:
byte = f.read(1)
while byte != b"":
# Do stuff with byte.
byte = f.read(1)
Ou comme le dit benhoyt, sautez le pas égal et tirez parti du fait que b""
est évalué à faux. Cela rend le code compatible entre 2.6 et 3.x sans aucune modification. Cela vous éviterait également de modifier la condition si vous passez du mode octet au texte ou l'inverse.
with open("myfile", "rb") as f:
byte = f.read(1)
while byte:
# Do stuff with byte.
byte = f.read(1)
Ce générateur génère des octets à partir d'un fichier, lisant le fichier par morceaux:
def bytes_from_file(filename, chunksize=8192):
with open(filename, "rb") as f:
while True:
chunk = f.read(chunksize)
if chunk:
for b in chunk:
yield b
else:
break
# example:
for b in bytes_from_file('filename'):
do_stuff_with(b)
Voir la documentation Python pour plus d'informations sur itérateurs et générateurs .
Si le fichier n'est pas trop volumineux, il est problématique de le conserver en mémoire:
bytes_read = open("filename", "rb").read()
for b in bytes_read:
process_byte(b)
où process_byte représente une opération que vous souhaitez effectuer sur l'octet transmis.
Si vous voulez traiter un morceau à la fois:
file = open("filename", "rb")
try:
bytes_read = file.read(CHUNKSIZE)
while bytes_read:
for b in bytes_read:
process_byte(b)
bytes_read = file.read(CHUNKSIZE)
finally:
file.close()
Pour lire un fichier - un octet à la fois (en ignorant la mise en mémoire tampon) - vous pouvez utiliser la fonction intégrée à deux arguments iter(callable, sentinel)
:
with open(filename, 'rb') as file:
for byte in iter(lambda: file.read(1), b''):
# Do stuff with byte
Il appelle file.read(1)
jusqu'à ce qu'il ne retourne rien b''
(chaîne d'octets vide). La mémoire ne devient pas illimitée pour les gros fichiers. Vous pouvez passer buffering=0
à open()
pour désactiver la mise en mémoire tampon - cela garantit qu'un seul octet est lu par itération (lent).
with
- statement ferme automatiquement le fichier - y compris le cas où le code ci-dessous déclenche une exception.
Malgré la présence de la mise en mémoire tampon interne par défaut, il est toujours inefficace de traiter un octet à la fois. Par exemple, voici l'utilitaire blackhole.py
qui mange tout ce qui lui est donné:
#!/usr/bin/env python3
"""Discard all input. `cat > /dev/null` analog."""
import sys
from functools import partial
from collections import deque
chunksize = int(sys.argv[1]) if len(sys.argv) > 1 else (1 << 15)
deque(iter(partial(sys.stdin.detach().read, chunksize), b''), maxlen=0)
Exemple:
$ dd if=/dev/zero bs=1M count=1000 | python3 blackhole.py
Il traite ~ 1,5 Go/s lorsque chunksize == 32768
sur ma machine et uniquement ~ 7,5 Mo/s quand chunksize == 1
. Autrement dit, il est 200 fois plus lent à lire un octet à la fois. Tenez-en compte si vous pouvez réécrire votre traitement pour utiliser plusieurs octets à la fois et si vous avez besoin de performances.
mmap
vous permet de traiter un fichier comme bytearray
et un objet de fichier simultanément. Cela peut servir d'alternative au chargement du fichier entier en mémoire si vous avez besoin d'accéder aux deux interfaces. En particulier, vous pouvez parcourir un octet à la fois sur un fichier mappé en mémoire en utilisant simplement une boucle for
- simple:
from mmap import ACCESS_READ, mmap
with open(filename, 'rb', 0) as f, mmap(f.fileno(), 0, access=ACCESS_READ) as s:
for byte in s: # length is equal to the current file size
# Do stuff with byte
mmap
supporte la notation slice. Par exemple, mm[i:i+len]
renvoie len
octets à partir du fichier commençant à la position i
. Le protocole de gestionnaire de contexte n'est pas pris en charge avant Python 3.2; vous devez appeler mm.close()
explicitement dans ce cas. Itérer sur chaque octet en utilisant mmap
consomme plus de mémoire que file.read(1)
, mais mmap
est plus rapide d'un ordre de grandeur.
Pour résumer tous les points brillants de chrispy, Skurmedel, Ben Hoyt et Peter Hansen, ce serait la solution optimale pour traiter un fichier binaire un octet à la fois:
with open("myfile", "rb") as f:
while True:
byte = f.read(1)
if not byte:
break
do_stuff_with(ord(byte))
Pour python versions 2.6 et supérieures, car:
Ou utilisez la solution J. F. Sebastians pour améliorer la vitesse
from functools import partial
with open(filename, 'rb') as file:
for byte in iter(partial(file.read, 1), b''):
# Do stuff with byte
Ou si vous le voulez en tant que fonction génératrice comme démontré par codeape:
def bytes_from_file(filename):
with open(filename, "rb") as f:
while True:
byte = f.read(1)
if not byte:
break
yield(ord(byte))
# example:
for b in bytes_from_file('filename'):
do_stuff_with(b)
Lecture du fichier binaire dans Python et lecture en boucle de chaque octet
La nouveauté de Python 3.5 est le module pathlib
, qui dispose d'une méthode pratique permettant de lire un fichier sous forme d'octets, ce qui nous permet d'effectuer une itération sur les octets. Je considère cela comme une bonne (si rapide et sale) réponse:
import pathlib
for byte in pathlib.Path(path).read_bytes():
print(byte)
Intéressant que c’est la seule réponse à mentionner pathlib
.
Dans Python 2, vous feriez probablement ceci (comme le suggère également Vinay Sajip):
with open(path, 'b') as file:
for byte in file.read():
print(byte)
Si le fichier est trop volumineux pour être itéré en mémoire, vous pouvez le découper de manière idiomatique à l’aide de la fonction iter
avec la signature callable, sentinel
- le Python 2. version:
with open(path, 'b') as file:
callable = lambda: file.read(1024)
sentinel = bytes() # or b''
for chunk in iter(callable, sentinel):
for byte in chunk:
print(byte)
(Plusieurs autres réponses le mentionnent, mais rares sont celles qui offrent une taille de lecture raisonnable.)
Créons une fonction pour cela, incluant les utilisations idiomatiques de la bibliothèque standard pour Python 3.5+:
from pathlib import Path
from functools import partial
from io import DEFAULT_BUFFER_SIZE
def file_byte_iterator(path):
"""given a path, return an iterator over the file
that lazily loads the file
"""
path = Path(path)
with path.open('rb') as file:
reader = partial(file.read1, DEFAULT_BUFFER_SIZE)
file_iterator = iter(reader, bytes())
for chunk in file_iterator:
for byte in chunk:
yield byte
Notez que nous utilisons file.read1
. file.read
bloque jusqu'à obtenir tous les octets demandés ou EOF
. file.read1
nous permet d'éviter les blocages et peut revenir plus rapidement pour cette raison. Aucune autre réponse ne le mentionne aussi bien.
Faisons un fichier avec un mégaoctet (en fait, un mégaoctet) de données pseudo-aléatoires:
import random
import pathlib
path = 'pseudorandom_bytes'
pathobj = pathlib.Path(path)
pathobj.write_bytes(
bytes(random.randint(0, 255) for _ in range(2**20)))
Maintenant, parcourons-le et matérialisons-le en mémoire:
>>> l = list(file_byte_iterator(path))
>>> len(l)
1048576
Nous pouvons inspecter n’importe quelle partie des données, par exemple les 100 et 100 premiers octets:
>>> l[-100:]
[208, 5, 156, 186, 58, 107, 24, 12, 75, 15, 1, 252, 216, 183, 235, 6, 136, 50, 222, 218, 7, 65, 234, 129, 240, 195, 165, 215, 245, 201, 222, 95, 87, 71, 232, 235, 36, 224, 190, 185, 12, 40, 131, 54, 79, 93, 210, 6, 154, 184, 82, 222, 80, 141, 117, 110, 254, 82, 29, 166, 91, 42, 232, 72, 231, 235, 33, 180, 238, 29, 61, 250, 38, 86, 120, 38, 49, 141, 17, 190, 191, 107, 95, 223, 222, 162, 116, 153, 232, 85, 100, 97, 41, 61, 219, 233, 237, 55, 246, 181]
>>> l[:100]
[28, 172, 79, 126, 36, 99, 103, 191, 146, 225, 24, 48, 113, 187, 48, 185, 31, 142, 216, 187, 27, 146, 215, 61, 111, 218, 171, 4, 160, 250, 110, 51, 128, 106, 3, 10, 116, 123, 128, 31, 73, 152, 58, 49, 184, 223, 17, 176, 166, 195, 6, 35, 206, 206, 39, 231, 89, 249, 21, 112, 168, 4, 88, 169, 215, 132, 255, 168, 129, 127, 60, 252, 244, 160, 80, 155, 246, 147, 234, 227, 157, 137, 101, 84, 115, 103, 77, 44, 84, 134, 140, 77, 224, 176, 242, 254, 171, 115, 193, 29]
Ne faites pas ce qui suit - ceci extrait un morceau de taille arbitraire jusqu'à ce qu'il atteigne un caractère de nouvelle ligne - trop lent quand les morceaux sont trop petits et éventuellement trop grands:
with open(path, 'rb') as file:
for chunk in file: # text newline iteration - not for bytes
for byte in chunk:
yield byte
Ce qui précède n’est valable que pour les fichiers texte lisibles par l’humanité (comme le texte brut, le code, le balisage, le balisage, etc ... essentiellement tout ce qui est ascii, utf, latin, etc ... codé).
Après avoir essayé tout ce qui précède et utilisé la réponse de @Aaron Hall, je rencontrais des erreurs de mémoire pour un fichier de ~ 90 Mo sur un ordinateur fonctionnant sous Windows 10, 8 Gb RAM et Python 3.5 32 bits. Un collègue m'a recommandé d'utiliser numpy
à la place et cela fonctionne à merveille.
De loin, le plus rapide à lire un fichier binaire complet (que j'ai testé) est:
import numpy as np
file = "binary_file.bin"
data = np.fromfile(file, 'u1')
Des multitudes plus rapides que toutes les autres méthodes jusqu'à présent. J'espère que ça aide quelqu'un!
Python 3, lisez tout le fichier à la fois:
with open("filename", "rb") as binary_file:
# Read the whole file at once
data = binary_file.read()
print(data)
Vous pouvez itérer ce que vous voulez en utilisant la variable data
.
Si vous avez beaucoup de données binaires à lire, vous pouvez envisager le struct module . Il est documenté en tant que conversion "entre les types C et Python", mais bien sûr, les octets sont des octets et peu importe que ceux-ci aient été créés en tant que types C. Par exemple, si vos données binaires contiennent deux entiers de 2 octets et un entier de 4 octets, vous pouvez les lire comme suit (exemple tiré de la documentation struct
):
>>> struct.unpack('hhl', b'\x00\x01\x00\x02\x00\x00\x00\x03')
(1, 2, 3)
Vous trouverez peut-être cela plus pratique, plus rapide, ou les deux, que de faire une boucle explicite sur le contenu d'un fichier.
si vous cherchez quelque chose de rapide, voici une méthode que j'utilise et qui fonctionne depuis des années:
from array import array
with open( path, 'rb' ) as file:
data = array( 'B', file.read() ) # buffer the file
# evaluate it's data
for byte in data:
v = byte # int value
c = chr(byte)
si vous voulez itérer des caractères au lieu d'ints, vous pouvez simplement utiliser data = file.read()
, qui devrait être un objet bytes () dans py3.