web-dev-qa-db-fra.com

Lecture de fichier binaire et boucle sur chaque octet

En Python, comment lire dans un fichier binaire et boucler chaque octet de ce fichier?

330
Jesse Vogt

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)
340
Skurmedel

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 .

161
codeape

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()
48
Vinay Sajip

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.

32
jfs

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:

  • tampons python en interne - pas besoin de lire des morceaux
  • Principe DRY - ne pas répéter la ligne de lecture
  • avec déclaration assure la fermeture d'un fichier propre
  • 'octet' est évalué à faux lorsqu'il n'y a plus d'octets (pas lorsqu'un octet vaut zéro)

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)
19
Holger Bille

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.)

Meilleure pratique pour les gros fichiers ou la lecture avec mémoire tampon/interactive

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.

Démonstration de l'utilisation des meilleures pratiques:

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 pas itérer par lignes pour les fichiers binaires

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é).

17
Aaron Hall

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')

référence

Des multitudes plus rapides que toutes les autres méthodes jusqu'à présent. J'espère que ça aide quelqu'un!

6
Rick M.

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.

6
Mircea

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.

4
gerrit

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.

2
Tcll