The io module in the standard library provides functions and classes for handling input and output operations. In this article we will focus on the BytesIO class.

The BytesIO class is used for creating in-memory byte streams that can be used as a file object. The created BytesIO object( commonly reffered to as a stream) has a file-like API, with methods like read() write(), readlines() and other file methods. 

To use the class we will first need to import it in our program, as shown below:

from io import BytesIO #import the class

print(BytesIO)

Basic Usage

The BytesIO constructor has the following syntax:

BytesIO(initial_bytes = b'')

The initial_bytes parameter specifies the initial bytes contents of the created BytesIO object. If initial_bytes is not given, an empty BytesIO will be created.

initialize a BytesIO object

from io import BytesIO

data = b'Hello, World'
stream = BytesIO(data)

print(stream.getvalue()) #read from the stream

stream.close()

In the above example we created a bytes stream with initial bytes data, the b prefix indicates that the string should be treated as a bytes literal rather than a regular string. 

The stream.getvalue() method returns the entire contents of the bytes stream as a bytes object.

After calling the close() method, the stream cannot be read from or written to. We can automate calling of this method by using the ByteIO object as a context manager i.e using with statement. This way the above example will look as follows:

from io import BytesIO

data = b'Hello, World'

with BytesIO(data) as stream:
     print(stream.getvalue()) #read from the stream

#the stream is now closed.

Reading and Writing bytes data

We can perform various operations on the BytesIO stream, just like we would on a regular file object. 

reading bytes

The read() method to reads and returns a specified number of bytes from the stream starting from the current position of the stream "cursor".

use read() to read from the stream

from io import BytesIO

data = b'Hello, World!'

with BytesIO(data) as stream:
    stream = BytesIO(data)
    stream.seek(0)

    print(stream.read(5)) #read 5 bytes from the stream

Note that in the above example, before calling the read() method, we first called the seek() method which moves the current position in the file to the beginning (position 0). This ensures that when we call the read() method, it will start reading the file from the beginning. Otherwise, it would start reading from wherever the 'cursor' position is, which in the above case is in the end of the file.

The readline() methods reads bytes until it meets the end of line character. Consider the following example:

from io import BytesIO

data = b"Hello, World!\nWelcome to Pynerds."

with BytesIO(data) as stream:
    stream.seek(0)

    print('line1: ', stream.readline()) #reads a single line
    print('line2: ', stream.readline()) #reads another line

To read multiple lines at once , we can use the readlines() method, which returns a list  of all lines in the stream starting from the position of the "cursor".

from io import BytesIO

data = b"Hello, World!\nWelcome to Pynerds."
stream = BytesIO(data)

stream.seek(0)
print(stream.readlines()) #read all lines

Writing to the stream

The write() method writes the specified bytes  into the stream, starting from the current point of the "cursor". It has the following syntax:

stream.write(data)

write to the stream

from io import BytesIO

with BytesIO() as stream:
     stream.write(b'Hello, World!\n')
     stream.write(b'Welcome to Pynerds.')
     
     print(stream.getvalue()) #get the contents

To write multiple lines at once you can use the writelines() method. The method takes an iterable containing the lines of bytes to be written.

from io import BytesIO

lines = [b'Hello, World\n', b'Welcome to Pynerds']

with BytesIO() as stream:
     stream.writelines(lines)
     
     print(stream.getvalue()) #get the contents

Note that the writelines() method does not add the end of line character between the lines, you have to do that yourself.

Other BytesIO methods and attributes

In addition to the methods we have discussed, BytesIO objects contains other methods, some of them are shown in the following table:

readable() Returns True if the BytesIO stream can be read from. Otherwise False.
seekable() Returns True if the stream can be seeked. Otherwise False
writable() Returns True if the stream can be written to. Otherwise False.
seek() Change the positions of the stream 'cursor' to the specified position. Defaults to 0, which is the beginning of the stream.
tell() Returns the current position of the stream 'cursor'.
truncate() Truncate the stream to a given size.