En Python, comment lire un fichier binaire et boucler sur 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 devrez l'importer:
from __future__ import with_statement
En 2.6, cela n'est pas nécessaire.
Python 3
En Python 3, c'est un peu différent. Nous n'obtiendrons plus les caractères bruts du flux en mode octet mais les 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 l'inégal et profitez du fait que la valeur est b""
fausse. Cela rend le code compatible entre 2.6 et 3.x sans aucune modification. Cela vous éviterait également de changer la condition si vous passez du mode octet au texte ou inversement.
with open("myfile", "rb") as f:
byte = f.read(1)
while byte:
# Do stuff with byte.
byte = f.read(1)
python 3.8
Désormais grâce à: = opérateur le code ci-dessus peut être écrit de manière plus courte.
with open("myfile", "rb") as f:
while (byte := f.read(1)):
# Do stuff with byte.
Ce générateur produit des octets à partir d'un fichier, en lisant le fichier par morceaux:
Consultez la documentation Python pour plus d'informations sur les itérateurs et les générateurs .
la source
8192 Byte = 8 kB
(en fait c'estKiB
mais ce n'est pas aussi connu). La valeur est "totalement" aléatoire mais 8 Ko semble être une valeur appropriée: pas trop de mémoire est gaspillée et il n'y a toujours pas "trop" d'opérations de lecture comme dans la réponse acceptée par Skurmedel ...for b in chunk:
boucle la plus intérieure paryield from chunk
. Cette forme de ayield
été ajoutée dans Python 3.3 (voir Expressions de rendement ).Si le fichier n'est pas trop gros, le conserver en mémoire est un problème:
où process_byte représente une opération que vous souhaitez effectuer sur l'octet transmis.
Si vous souhaitez traiter un morceau à la fois:
L'
with
instruction est disponible en Python 2.5 et supérieur.la source
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)
:Il appelle
file.read(1)
jusqu'à ce qu'il ne retourne rienb''
(bytestring vide). La mémoire ne devient pas illimitée pour les fichiers volumineux. Vous pouvez passerbuffering=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 le fichier automatiquement - y compris le cas où le code en dessous déclenche une exception.Malgré la présence de tampons internes par défaut, il est toujours inefficace de traiter un octet à la fois. Par exemple, voici l'
blackhole.py
utilitaire qui mange tout ce qui lui est donné:Exemple:
Il traite ~ 1,5 Go / s lorsque
chunksize == 32768
sur ma machine et seulement ~ 7,5 Mo / s quandchunksize == 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 unbytearray
et un objet fichier simultanément. Il peut servir d'alternative au chargement de l'ensemble du fichier 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 unefor
boucle simple :mmap
prend en charge la notation de tranche. Par exemple,mm[i:i+len]
retourne deslen
octets du fichier à partir de la positioni
. Le protocole du gestionnaire de contexte n'est pas pris en charge avant Python 3.2; vous devez appelermm.close()
explicitement dans ce cas. L'itération sur chaque octet en utilisantmmap
consomme plus de mémoire quefile.read(1)
, maismmap
est plus rapide d'un ordre de grandeur.la source
numpy
tableaux (octets) mappés en mémoire équivalents .numpy.memmap()
et vous pouvez obtenir les données un octet à la fois (ctypes.data). Vous pourriez penser que les tableaux numpy sont juste un peu plus que des blobs en mémoire + métadonnées.Nouveau dans Python 3.5 est le
pathlib
module, qui a une méthode pratique spécifiquement pour lire dans un fichier en octets, nous permettant d'itérer sur les octets. Je considère que c'est une réponse décente (si rapide et sale):Il est intéressant de noter que c'est la seule réponse à mentionner
pathlib
.En Python 2, vous feriez probablement cela (comme le suggère également Vinay Sajip):
Dans le cas où le fichier peut être trop volumineux pour itérer sur la mémoire, vous le découpez idiomatiquement, en utilisant la
iter
fonction avec lacallable, sentinel
signature - la version Python 2:(Plusieurs autres réponses le mentionnent, mais peu offrent une taille de lecture raisonnable.)
Meilleure pratique pour les fichiers volumineux ou la lecture en mémoire tampon / interactive
Créons une fonction pour ce faire, y compris des utilisations idiomatiques de la bibliothèque standard pour Python 3.5+:
Notez que nous utilisons
file.read1
.file.read
bloque jusqu'à ce qu'il obtienne tous les octets demandés ouEOF
.file.read1
nous permet d'éviter le blocage, et il peut revenir plus rapidement à cause de cela. Aucune autre réponse ne le mentionne également.Démonstration de l'utilisation des meilleures pratiques:
Faisons un fichier avec un mégaoctet (en fait mégaoctet) de données pseudo-aléatoires:
Maintenant, parcourons-le et matérialisons-le en mémoire:
Nous pouvons inspecter n'importe quelle partie des données, par exemple, les 100 derniers et les 100 premiers octets:
Ne pas parcourir les lignes pour les fichiers binaires
Ne faites pas ce qui suit - cela tire un morceau de taille arbitraire jusqu'à ce qu'il atteigne un caractère de nouvelle ligne - trop lent lorsque les morceaux sont trop petits, et peut-être aussi trop gros:
Ce qui précède n'est bon que pour les fichiers texte lisibles sémantiquement (comme le texte brut, le code, le balisage, le démarquage, etc. ... essentiellement tout ce qui est encodé en ascii, utf, latin, etc ...) que vous devez ouvrir sans le
'b'
drapeau.la source
path = Path(path), with path.open('rb') as file:
plutôt que d'utiliser la fonction ouverte intégrée à la place? Ils font tous les deux la même chose, n'est-ce pas?Path
objet car c'est une nouvelle façon très pratique de gérer les chemins. Au lieu de passer une chaîne dans les fonctions "à droite" soigneusement choisies, nous pouvons simplement appeler les méthodes sur l'objet chemin, qui contient essentiellement la plupart des fonctionnalités importantes que vous souhaitez avec ce qui est sémantiquement une chaîne chemin. Avec les IDE qui peuvent inspecter, nous pouvons également obtenir plus facilement la saisie semi-automatique. Nous pourrions accomplir la même chose avec le programmeopen
intégré, mais il y a beaucoup d'avantages lors de l'écriture du programme pour que le programmeur utilise l'Path
objet à la place.file_byte_iterator
est beaucoup plus rapide que toutes les méthodes que j'ai essayées sur cette page. Bravo à vous!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:
Pour les versions python 2.6 et supérieures, car:
Ou utilisez la solution JF Sebastians pour une vitesse améliorée
Ou si vous le souhaitez en tant que fonction de générateur comme démontré par codeape:
la source
Python 3, lisez tout le fichier à la fois:
Vous pouvez répéter ce que vous voulez en utilisant
data
variable.la source
Après avoir essayé tout ce qui précède et utilisé la réponse de @Aaron Hall, j'obtenais des erreurs de mémoire pour un fichier de ~ 90 Mo sur un ordinateur exécutant Windows 10, 8 Go de RAM et Python 3.5 32 bits. Un collègue m'a recommandé d'utiliser
numpy
place et cela fonctionne à merveille.De loin, le plus rapide pour lire un fichier binaire entier (que j'ai testé) est:
Référence
Multitudes plus rapides que toutes les autres méthodes jusqu'à présent. J'espère que cela aide quelqu'un!
la source
numpy
, cela pourrait valoir la peine.Si vous avez beaucoup de données binaires à lire, vous pouvez envisager le module struct . Il est documenté comme convertissant "entre les types C et Python", mais bien sûr, les octets sont des octets, et peu importe si ceux-ci ont é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
struct
documentation):Vous trouverez peut-être cela plus pratique, plus rapide ou les deux que d'effectuer une boucle explicite sur le contenu d'un fichier.
la source
Ce message lui-même n'est pas une réponse directe à la question. Il s'agit plutôt d'un référentiel extensible basé sur les données qui peut être utilisé pour comparer de nombreuses réponses (et des variantes d'utilisation de nouvelles fonctionnalités ajoutées dans des versions plus récentes et plus modernes de Python) qui ont été publiées sur cette question - et devraient donc être utile pour déterminer laquelle a les meilleures performances.
Dans quelques cas, j'ai modifié le code dans la réponse référencée pour le rendre compatible avec le framework de référence.
Tout d'abord, voici les résultats pour ce qui sont actuellement les dernières versions de Python 2 & 3:
Je l'ai également exécuté avec un fichier de test de 10 Mio beaucoup plus volumineux (qui a pris près d'une heure à fonctionner) et j'ai obtenu des résultats de performance comparables à ceux indiqués ci-dessus.
Voici le code utilisé pour effectuer l'analyse comparative:
la source
yield from chunk
placefor byte in chunk: yield byte
? Je pense que je devrais resserrer ma réponse avec ça.yield from
.enumerate
car l'itération doit être comprise comme terminée - sinon, j'ai vérifié en dernier - énumérer a un peu de frais généraux avec les coûts de la comptabilité pour l'index avec + = 1, vous pouvez donc alternativement faire la comptabilité dans votre propre code. Ou même passer à un deque avecmaxlen=0
.enumerate
. Merci pour les commentaires. Ajoutera une mise à jour à mon message qui ne l'a pas (bien que je ne pense pas que cela change beaucoup les résultats). Ajoutera également lanumpy
réponse basée sur @Rick M.super().
au lieu detuple.
dans votre,__new__
vous pourriez utiliser lesnamedtuple
noms d'attribut au lieu d'index.si vous cherchez quelque chose de rapide, voici une méthode que j'utilise et qui fonctionne depuis des années:
si vous voulez itérer les caractères au lieu des entiers, vous pouvez simplement utiliser
data = file.read()
, qui devrait être un objet bytes () dans py3.la source