python leer un - Leyendo archivo binario y repitiendo sobre cada byte





4 Answers

Este generador produce bytes de un archivo, leyendo el archivo en trozos:

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)

Consulte la documentación de Python para obtener información sobre iterators y generators .

archivos binarios columnas

En Python, ¿cómo leo un archivo binario y recorro cada byte de ese archivo?




Para leer un archivo, un byte a la vez (ignorando el almacenamiento en búfer), puede usar la función incorporada iter(callable, sentinel) dos argumentos :

with open(filename, 'rb') as file:
    for byte in iter(lambda: file.read(1), b''):
        # Do stuff with byte

Llama a file.read(1) hasta que no devuelve nada b'' (bytestring vacío). La memoria no crece ilimitada para archivos grandes. Podría pasar el buffering=0 para open() , deshabilitar el búfer; garantiza que solo se lea un byte por iteración (lento).

with -statement cierra el archivo automáticamente, incluido el caso en el que el código debajo genera una excepción.

A pesar de la presencia de almacenamiento en búfer interno por defecto, todavía es ineficiente procesar un byte a la vez. Por ejemplo, aquí está la utilidad blackhole.py que come todo lo que se le da:

#!/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)

Ejemplo:

$ dd if=/dev/zero bs=1M count=1000 | python3 blackhole.py

Procesa ~ 1.5 GB / s cuando chunksize == 32768 en mi máquina y solo ~ 7.5 MB / s cuando chunksize == 1 . Es decir, es 200 veces más lento leer un byte a la vez. Téngalo en cuenta si puede reescribir su procesamiento para usar más de un byte a la vez y si necesita rendimiento.

mmap permite tratar un archivo como un bytearray y un objeto de archivo simultáneamente. Puede servir como una alternativa para cargar todo el archivo en la memoria si necesita acceder a ambas interfaces. En particular, puede iterar un byte a la vez sobre un archivo asignado en memoria simplemente usando un plano for -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 soporta la notación de corte. Por ejemplo, mm[i:i+len] devuelve len bytes del archivo que comienza en la posición i . El protocolo del administrador de contexto no es compatible antes de Python 3.2; necesita llamar a mm.close() explícitamente en este caso. Iterar sobre cada byte usando mmap consume más memoria que file.read(1) , pero mmap es un orden de magnitud más rápido.




Leyendo un archivo binario en Python y haciendo un bucle sobre cada byte

Lo nuevo en Python 3.5 es el módulo pathlib , que tiene un método de conveniencia específicamente para leer un archivo como bytes, lo que nos permite iterar sobre los bytes. Considero que esta es una respuesta decente (aunque rápida y sucia):

import pathlib

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

Es interesante que esta es la única respuesta para mencionar pathlib .

En Python 2, probablemente harías esto (como sugiere Vinay Sajip):

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

En el caso de que el archivo sea demasiado grande para iterarlo en la memoria, lo fragmentaría, idiomáticamente, usando la función iter con la firma callable, sentinel la versión de 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)

(Varias otras respuestas mencionan esto, pero pocas ofrecen un tamaño de lectura razonable).

Mejores prácticas para archivos grandes o lectura interactiva / almacenada en búfer

Vamos a crear una función para hacer esto, incluidos los usos idiomáticos de la biblioteca estándar para 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

Tenga en cuenta que usamos file.read1 . file.read bloquea hasta que obtiene todos los bytes solicitados de él o EOF . file.read1 nos permite evitar el bloqueo y puede regresar más rápidamente debido a esto. Ninguna otra respuesta menciona esto también.

Demostración del uso de las mejores prácticas:

Hagamos un archivo con un megabyte (en realidad mebibyte) de datos pseudoaleatorios:

import random
import pathlib
path = 'pseudorandom_bytes'
pathobj = pathlib.Path(path)

pathobj.write_bytes(
  bytes(random.randint(0, 255) for _ in range(2**20)))

Ahora vamos a iterar sobre él y materializarlo en la memoria:

>>> l = list(file_byte_iterator(path))
>>> len(l)
1048576

Podemos inspeccionar cualquier parte de los datos, por ejemplo, los últimos 100 y los primeros 100 bytes:

>>> 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]

No iterar por líneas para archivos binarios

No haga lo siguiente: esto extrae un trozo de tamaño arbitrario hasta que llega a un carácter de nueva línea, demasiado lento cuando los trozos son demasiado pequeños y posiblemente demasiado grandes también:

    with open(path, 'rb') as file:
        for chunk in file: # text newline iteration - not for bytes
            for byte in chunk:
                yield byte

Lo anterior solo es válido para lo que son archivos de texto legibles semánticamente humanos (como texto sin formato, código, marcado, marcado, etc.) esencialmente cualquier cosa ASCII, utf, latin, etc ... codificada.




Si tiene una gran cantidad de datos binarios para leer, puede considerar el módulo de estructura . Está documentado como la conversión de "entre los tipos C y Python", pero, por supuesto, los bytes son bytes, y no importa si esos se crearon como tipos C. Por ejemplo, si sus datos binarios contienen dos enteros de 2 bytes y un entero de 4 bytes, puede leerlos de la siguiente manera (ejemplo tomado de la documentación de la struct ):

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

Puede encontrar esto más conveniente, más rápido, o ambos, que hacer un bucle explícito sobre el contenido de un archivo.




Related