python décoder - Lecture du fichier binaire et bouclage sur chaque octet





texte une (9)


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 d'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 les versions python 2.6 et supérieures, car:

  • tampons python en interne - pas besoin de lire des morceaux
  • Principe DRY - ne répète pas la ligne de lecture
  • avec la déclaration assure un fichier propre à proximité
  • 'byte' est évalué à false quand il n'y a plus d'octets (pas quand un octet est nul)

Ou utilisez la solution JF 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 comme une fonction de générateur 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)

En Python, comment lire un fichier binaire et faire une boucle sur chaque octet de ce fichier?




Pour lire un fichier - un octet à la fois (en ignorant la mise en mémoire tampon) - vous pouvez utiliser la fonction intégrée iter(callable, sentinel) 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 renvoie rien b'' (bytestring vide). La mémoire ne devient pas illimitée pour les fichiers volumineux. Vous pouvez passer buffering=0 à open() , pour désactiver la mise en mémoire tampon - il garantit que seul un octet est lu par itération (lent).

with -statement ferme le fichier automatiquement - y compris le cas où le code en dessous soulève 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 GB / s quand chunksize == 32768 sur ma machine et seulement ~ 7.5 MB / s quand chunksize == 1 . Autrement dit, il est 200 fois plus lent de lire un octet à la fois. Prenez en compte si vous pouvez réécrire votre traitement pour utiliser plus d'un octet à la fois et si vous avez besoin de performance.

mmap vous permet de traiter un fichier comme un bytearray et un objet fichier simultanément. Il 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 itérer un octet à la fois sur un fichier mappé en mémoire en utilisant simplement un point -loop:

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 de tranche. Par exemple, mm[i:i+len] renvoie len octets du fichier à partir de la position i . Le protocole du gestionnaire de contexte n'est pas supporté 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 un ordre de grandeur plus rapide.




Si vous cherchez quelque chose rapidement, voici une méthode que j'ai utilisée qui a fonctionné pendant des années:

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 des ints, vous pouvez simplement utiliser data = file.read() , qui devrait être un objet bytes () dans py3.




Si le fichier n'est pas trop gros, le garder en mémoire est un problème:

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



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 si ceux-ci ont été créés en tant que types C n'a pas d'importance. 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 de la struct ):

>>> struct.unpack('hhl', b'\x00\x01\x00\x02\x00\x00\x00\x03')
(1, 2, 3)

Vous pourriez trouver cela plus pratique, plus rapide, ou les deux, que de boucler explicitement le contenu d'un fichier.




Lecture du fichier binaire en Python et bouclage sur chaque octet

Nouveau dans Python 3.5 est le module pathlib , qui a une méthode de commodité spécifique pour lire dans un fichier en octets, ce qui nous permet d'itérer sur les octets. Je considère que c'est une réponse correcte (si rapide et sale):

import pathlib

for byte in pathlib.Path(path).read_bytes():
    print(byte)

Intéressant que c'est la seule réponse à mentionner pathlib .

En Python 2, vous feriez probablement cela (comme le suggère Vinay Sajip):

with open(path, 'b') as file:
    for byte in file.read():
        print(byte)

Dans le cas où le fichier pourrait être trop volumineux pour itérer en mémoire, vous le morceleriez, idiomatiquement, en utilisant la fonction iter avec la signature callable, sentinel la version Python 2:

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 peu offrent une taille de lecture raisonnable.)

Meilleure pratique pour les fichiers volumineux ou la lecture tamponnée / interactive

Créons une fonction pour cela, y compris 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 le blocage, et il peut revenir plus rapidement à cause de cela. Aucune autre réponse ne le mentionne aussi.

Démonstration de l'utilisation des meilleures pratiques:

Faisons un fichier avec un mégaoctet (en fait un mébioctet) 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)))

Passons maintenant dessus 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 derniers et les 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 des lignes pour les fichiers binaires

Ne faites pas ce qui suit - cela tire un morceau de taille arbitraire jusqu'à ce qu'il arrive à un caractère de retour à la ligne - trop lent lorsque les morceaux sont trop petits, et peut-être trop grand aussi:

    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 est seulement bon pour ce qui sont des fichiers texte sémantiquement lisibles par l'homme (comme le texte brut, le code, le balisage, la démarque etc ... essentiellement tout ce qui est encodé en ascii, utf, latin, etc ...).




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 data variable de data .




f = open("myfile", "rb")
try:
    byte = f.read(1)
    while byte != "":
        # Do stuff with byte.
        byte = f.read(1)
finally:
    f.close()

Par suggestion de chrispy:

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 en dessous de 2.5. Pour l'utiliser dans v 2.5, vous devez l'importer:

from __future__ import with_statement

En 2.6 ce n'est pas nécessaire.

En Python 3, c'est un peu différent. Nous n'obtiendrons plus de caractères bruts du flux en mode octet mais en 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 non égal et profitez du fait que b"" évalue à faux. Cela rend le code compatible entre 2.6 et 3.x sans aucun changement. Cela vous éviterait également de changer la condition si vous passez du mode byte au texte ou inversement.

with open("myfile", "rb") as f:
    byte = f.read(1)
    while byte:
        # Do stuff with byte.
        byte = f.read(1)



Utilisation:

import os

cmd = 'ls -al'

os.system(cmd)

os - Ce module fournit un moyen portable d'utiliser des fonctionnalités dépendant du système d'exploitation.

Pour la os fonctions os , here la documentation.





python file-io binary