Diferencia entre `open` y` io.BytesIO` en flujos binarios

Estoy aprendiendo a trabajar con streams en Python y noté que los documentos de IO dicen lo siguiente:

La forma más fácil de crear un flujo binario es con open () con ‘b’ en la cadena de modo:

f = open("myfile.jpg", "rb")

Los flujos binarios en memoria también están disponibles como objetos BytesIO:

f = io.BytesIO(b"some initial binary data: \x00\x01")

¿Cuál es la diferencia entre f como se define por open y f como se define por BytesIO ? En otras palabras, ¿qué hace un “flujo binario en memoria” y en qué se diferencia de lo que hace open ?

Por simplicidad, consideremos escribir en lugar de leer por ahora.

Entonces cuando usas open() como decir:

 with open("test.dat", "wb") as f: f.write(b"Hello World") f.write(b"Hello World") f.write(b"Hello World") 

Después de ejecutar eso, se test.dat un archivo llamado test.dat , que contiene Hello World . Los datos no se guardarán en la memoria después de que se escriban en el archivo (a menos que se guarden con un nombre).

Ahora cuando consideras io.BytesIO() lugar:

 with io.BytesIO() as f: f.write(b"Hello World") f.write(b"Hello World") f.write(b"Hello World") 

Que en lugar de escribir el contenido en un archivo, se escribe en un búfer de memoria. En otras palabras, un trozo de RAM. Esencialmente escribir lo siguiente sería el equivalente:

 buffer = b"" buffer += b"Hello World" buffer += b"Hello World" buffer += b"Hello World" 

En relación con el ejemplo con la instrucción with, al final también habrá un del buffer .

La diferencia clave aquí es la optimización y el rendimiento. io.BytesIO puede hacer algunas optimizaciones que lo hacen más rápido que simplemente concatenar todos los b"Hello World" uno por uno.

Solo para demostrarlo aquí hay un pequeño punto de referencia:

  • Concat: 1.3529 segundos
  • BytesIO: 0.0090 segundos
 import io import time begin = time.time() buffer = b"" for i in range(0, 50000): buffer += b"Hello World" end = time.time() seconds = end - begin print("Concat:", seconds) begin = time.time() buffer = io.BytesIO() for i in range(0, 50000): buffer.write(b"Hello World") end = time.time() seconds = end - begin print("BytesIO:", seconds) 

Además de la ganancia de rendimiento, el uso de BytesIO lugar de la concatenación tiene la ventaja de que se puede usar BytesIO en lugar de un objeto de archivo. Entonces, digamos que tiene una función que espera que se escriba un objeto de archivo. Luego puedes darle ese búfer en memoria en lugar de un archivo.

La diferencia es que open("myfile.jpg", "rb") simplemente carga y devuelve el contenido de myfile.jpg ; mientras que, BytesIO nuevamente es solo un búfer que contiene algunos datos.

Dado que BytesIO es solo un búfer, si desea escribir el contenido en un archivo más adelante, deberá hacerlo:

 buffer = io.BytesIO() # ... with open("test.dat", "wb") as f: f.write(buffer.getvalue()) 

Además, no mencionaste una versión; Estoy usando Python 3. Relacionado con los ejemplos: Estoy usando la instrucción with en lugar de llamar a f.close()

El uso de open abre un archivo en su disco duro. Según el modo que use, puede leer o escribir (o ambos) desde el disco.

Un objeto BytesIO no está asociado con ningún archivo real en el disco. Es solo una parte de la memoria que se comporta como lo hace un archivo. Tiene la misma API que un objeto de archivo devuelto desde open (con el modo r+b , que permite leer y escribir datos binarios).

BytesIO (y es BytesIO , un hermano StringIO que siempre está en modo de texto) puede ser útil cuando necesita pasar datos StringIO desde una API que espera recibir un objeto de archivo, pero donde prefiere pasar los datos directamente. Puede cargar sus datos de entrada que tiene en BytesIO antes de entregarlos a la biblioteca. Después de que regrese, puede obtener todos los datos que la biblioteca escribió en el archivo de BytesIO utilizando el método getvalue() . (Por lo general, solo necesitarías hacer uno de esos, por supuesto).

 f = open("myfile.jpg", "rb") 

lea los bytes del archivo del disco y asigne dicho valor al objeto referenciado como ‘f’ que Python guarda en la memoria.

 f = io.BytesIO(b"some initial binary data: \x00\x01") 

asigne el valor del flujo de bytes al objeto al que se hace referencia como ‘f’ que Python mantiene en la memoria.