En Python, ¿cómo leo en un archivo binario y hago un bucle sobre cada byte de ese archivo?
Python 2.4 y anteriores
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)
Tenga en cuenta que la declaración with no está disponible en versiones de Python inferiores a 2.5. Para usarlo en v 2.5 necesitarás importarlo:
from __future__ import with_statement
En 2.6 esto no es necesario.
Python 3
En Python 3, es un poco diferente. Ya no obtendremos caracteres sin procesar de la secuencia en modo byte sino objetos de byte, por lo tanto, debemos modificar la condición:
with open("myfile", "rb") as f:
byte = f.read(1)
while byte != b"":
# Do stuff with byte.
byte = f.read(1)
O como dice benhoyt, omita lo no igual y aproveche el hecho de que se b""
evalúa como falso. Esto hace que el código sea compatible entre 2.6 y 3.x sin ningún cambio. También le evitaría cambiar la condición si pasa del modo byte al texto o al revés.
with open("myfile", "rb") as f:
byte = f.read(1)
while byte:
# Do stuff with byte.
byte = f.read(1)
python 3.8
De ahora en adelante gracias a: = operador, el código anterior se puede escribir de una manera más corta.
with open("myfile", "rb") as f:
while (byte := f.read(1)):
# Do stuff with byte.
Este generador produce bytes de un archivo, leyendo el archivo en fragmentos:
Consulte la documentación de Python para obtener información sobre iteradores y generadores .
fuente
8192 Byte = 8 kB
(en realidad es,KiB
pero eso no es tan conocido). El valor es "totalmente" aleatorio, pero 8 kB parece ser un valor apropiado: no se desperdicia demasiada memoria y todavía no hay "demasiadas" operaciones de lectura como en la respuesta aceptada por Skurmedel ...for b in chunk:
ciclo más interno conyield from chunk
. Esta forma deyield
se agregó en Python 3.3 (ver Expresiones de rendimiento ).Si el archivo no es demasiado grande, mantenerlo en la memoria es un problema:
donde process_byte representa alguna operación que desea realizar en el byte pasado.
Si desea procesar un fragmento a la vez:
La
with
declaración está disponible en Python 2.5 y superior.fuente
Para leer un archivo, un byte a la vez (ignorando el almacenamiento en búfer), puede usar la función incorporada de dos argumentos
iter(callable, sentinel)
:Llama
file.read(1)
hasta que no devuelve nadab''
(cadena de bytes vacía). La memoria no crece de forma ilimitada para archivos grandes. Puede pasarbuffering=0
aopen()
, para deshabilitar el almacenamiento en búfer: garantiza que solo se lea un byte por iteración (lento).with
-declaración cierra el archivo automáticamente, incluido el caso en que el código debajo genera una excepción.A pesar de la presencia de almacenamiento en búfer interno de forma predeterminada, sigue siendo ineficiente procesar un byte a la vez. Por ejemplo, aquí está la
blackhole.py
utilidad que se come todo lo que se le da:Ejemplo:
Procesa ~ 1.5 GB / s cuando está
chunksize == 32768
en mi máquina y solo ~ 7.5 MB / s cuandochunksize == 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
le permite tratar un archivo como unbytearray
objeto de archivo y simultáneamente. Puede servir como una alternativa a 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 mapeado en memoria simplemente usando unfor
bucle simple :mmap
admite la notación de corte. Por ejemplo,mm[i:i+len]
devuelvelen
bytes del archivo comenzando en la posicióni
. El protocolo del administrador de contexto no es compatible antes de Python 3.2; necesita llamarmm.close()
explícitamente en este caso. Iterar sobre cada byte usandommap
consume más memoria quefile.read(1)
, perommap
es un orden de magnitud más rápido.fuente
numpy
matrices equivalentes asignadas en memoria (byte).numpy.memmap()
y puede obtener los datos de un byte a la vez (ctypes.data). Se podría pensar en matrices numpy como solo un poco más que blobs en memoria + metadatos.Nuevo en Python 3.5 es el
pathlib
módulo, que tiene un método conveniente específicamente para leer en un archivo como bytes, lo que nos permite iterar sobre los bytes. Considero que esta es una respuesta decente (aunque rápida y sucia):Es interesante que esta sea la única respuesta para mencionar
pathlib
.En Python 2, probablemente harías esto (como también sugiere Vinay Sajip):
En el caso de que el archivo sea demasiado grande para iterar sobre la memoria, lo fragmentaría, idiomáticamente, utilizando la
iter
función con lacallable, sentinel
firma, la versión de Python 2:(Varias otras respuestas mencionan esto, pero pocas ofrecen un tamaño de lectura razonable).
La mejor práctica para archivos grandes o lectura almacenada en búfer / interactiva
Creemos una función para hacer esto, incluidos los usos idiomáticos de la biblioteca estándar para Python 3.5+:
Tenga en cuenta que usamos
file.read1
.file.read
bloquea hasta que obtiene todos los bytes solicitados oEOF
.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 mejores prácticas:
Hagamos un archivo con un megabyte (en realidad mebibyte) de datos pseudoaleatorios:
Ahora vamos a iterar sobre él y materializarlo en la memoria:
Podemos inspeccionar cualquier parte de los datos, por ejemplo, los últimos 100 y los primeros 100 bytes:
No itere 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:
Lo anterior solo es bueno para lo que son archivos de texto legibles semánticamente humanos (como texto plano, código, marcado, marcado, etc., esencialmente cualquier cosa codificada en ASCII, UTF, latín, etc.) que debe abrir sin la
'b'
bandera.fuente
path = Path(path), with path.open('rb') as file:
lugar de usar la función abierta incorporada? Ambos hacen lo mismo, ¿correcto?Path
objeto porque es una nueva forma muy conveniente de manejar rutas. En lugar de pasar una cadena a las funciones "correctas" cuidadosamente elegidas, simplemente podemos llamar a los métodos en el objeto de ruta, que esencialmente contiene la mayor parte de la funcionalidad importante que desea con lo que semánticamente es una cadena de ruta. Con IDEs que pueden inspeccionar, también podemos obtener más fácilmente el autocompletado. Podríamos lograr lo mismo con elopen
incorporado, pero hay muchas ventajas al escribir el programa para que el programador use elPath
objeto en su lugar.file_byte_iterator
es mucho más rápido que todos los métodos que he probado en esta página. ¡Felicitaciones a usted!Para resumir todos los puntos brillantes de chrispy, Skurmedel, Ben Hoyt y Peter Hansen, esta sería la solución óptima para procesar un archivo binario de un byte a la vez:
Para python versiones 2.6 y superiores, porque:
O use la solución JF Sebastians para mejorar la velocidad
O si lo desea como una función generadora como lo demuestra codeape:
fuente
Python 3, lea todo el archivo a la vez:
Puedes iterar lo que quieras usando la
data
variable.fuente
Después de probar todo lo anterior y usar la respuesta de @Aaron Hall, recibía errores de memoria para un archivo de ~ 90 Mb en una computadora con Windows 10, 8 Gb RAM y Python 3.5 de 32 bits. Un colega me recomendó usar
numpy
en su lugar y funciona de maravilla.De lejos, el más rápido para leer un archivo binario completo (que he probado) es:
Referencia
Multitudes más rápidas que cualquier otro método hasta ahora. Espero que ayude a alguien!
fuente
numpy
, entonces podría valer la pena.Si tiene muchos datos binarios para leer, puede considerar el módulo de estructura . Está documentado como una conversión "entre tipos C y Python", pero por supuesto, los bytes son bytes, y si esos fueron creados como tipos C no importa. 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
struct
documentación):Puede encontrar esto más conveniente, más rápido o ambos, que hacer un bucle explícito sobre el contenido de un archivo.
fuente
Esta publicación en sí no es una respuesta directa a la pregunta. En cambio, es un punto de referencia extensible basado en datos que se puede utilizar para comparar muchas de las respuestas (y las variaciones de la utilización de nuevas características agregadas en versiones posteriores, más modernas, de Python) que se han publicado en esta pregunta, y por lo tanto deberían Ser útil para determinar cuál tiene el mejor rendimiento.
En algunos casos, modifiqué el código en la respuesta referenciada para que sea compatible con el marco de referencia.
Primero, aquí están los resultados de lo que actualmente son las últimas versiones de Python 2 y 3:
También lo ejecuté con un archivo de prueba de 10 MiB mucho más grande (que tardó casi una hora en ejecutarse) y obtuve resultados de rendimiento que eran comparables a los mostrados anteriormente.
Aquí está el código utilizado para hacer la evaluación comparativa:
fuente
yield from chunk
lugarfor byte in chunk: yield byte
? Estoy pensando que debería ajustar mi respuesta con eso.yield from
.enumerate
ya que debe entenderse que la iteración se completa; si no, la última vez que lo verifiqué, enumerate tiene un poco de sobrecarga con los costos sobre la contabilidad para el índice con + = 1, por lo que alternativamente puede hacer la contabilidad en su código propio O incluso pasar a una deque conmaxlen=0
.enumerate
. Gracias por la respuesta. Agregaré una actualización a mi publicación que no la tiene (aunque no creo que cambie mucho los resultados). También agregará lanumpy
respuesta basada en @Rick M.super().
lugar detuple.
en su__new__
podría usar losnamedtuple
nombres de los atributos en lugar de los índices.Si está buscando algo rápido, aquí hay un método que he estado usando que funcionó durante años:
si desea iterar caracteres en lugar de ints, simplemente puede usar
data = file.read()
, que debería ser un objeto bytes () en py3.fuente