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

5 minutos de lectura

avatar de usuario
Lucas Whyte

Estoy aprendiendo a trabajar con flujos en Python y noté que el documentos de OI decir lo siguiente:

La forma más sencilla 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 Según lo definido por open y f Según lo definido por BytesIO. En otras palabras, ¿qué hace un “flujo binario en memoria” y en qué se diferencia de lo que open ¿lo hace?

avatar de usuario
vallentín

En aras de la simplicidad, consideremos escribir en lugar de leer por ahora.

Así que 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, un archivo llamado test.dat se creará, conteniendo 3x 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() en cambio:

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, una porción 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 declaración with, entonces al final también habría una del buffer.

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

Solo para probarlo, 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 en lugar de concatenar tiene la ventaja de que BytesIO se puede utilizar en lugar de un objeto de archivo. Supongamos que tiene una función que espera que se escriba un objeto de archivo. Entonces puede 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, BytesIO nuevamente es solo un búfer que contiene algunos datos.

Ya que BytesIO es solo un búfer; si quisiera escribir el contenido en un archivo más tarde, tendría que hacer lo siguiente:

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 f.close()

  • Gran respuesta; Menciones de preguntas in memory stream y te has referido in memory buffer. ¿Hay alguna diferencia en Python? Valdría la pena abordarlo brevemente. Desde una perspectiva semántica inglesa, stream implica un flujo continuo de bits desde la fuente al sumidero (empujando desde la fuente), donde el búfer implica una memoria caché de bits en la fuente lista para la obtención rápida de fragmentos o piezas de la fuente (el sumidero tirando de la fuente).

    – Davos

    13 de abril de 2018 a las 13:10

  • Ejecuté el pequeño punto de referencia en mi máquina y obtuve un resultado similar usando Python3.5, sin embargo, cuando uso Python 2.7, “Concat” y “BytesIO” toman un tiempo similar, “Concat” es incluso un poco mejor. ¿Nada malo? esto me confunde.

    – JenkinsY

    13 de julio de 2018 a las 6:39

  • @Vallentin, lo siento, te equivocaste cuando dijiste “open(“myfile.jpg”, “rb”) simplemente carga y devuelve el contenido de myfile.jpg”, consulta rhis import io f = open("myfile.jpg", "rb") <class '_io.BufferedReader'> >>> isinstance(f, io.BufferedIOBase) True

    – Yahya Yahyaoui

    20 de marzo de 2019 a las 22:24

  • Gran respuesta, ¡me alegro de que hayas cubierto cómo volver a escribir el búfer en un archivo según sea necesario!

    – Elías Yishak

    4 abr a las 14:34

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

A BytesIO El objeto no está asociado con ningún archivo real en el disco. Es solo una porción de memoria que se comporta como un archivo. Tiene la misma API que un objeto de archivo devuelto por open (con modo r+bpermitiendo la lectura y escritura de datos binarios).

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

¿Ha sido útil esta solución?