Skip to Content
Course content

324: Working with bytes and bytearray

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Up until now, we've spent most of our time dealing with strings. But in the real world, software engineers often have to step away from human-readable text and deal with raw binary data—whether that's reading a JPEG, communicating with a piece of hardware via a socket, or parsing a custom file format. That's where bytes and bytearray come in.

To show you how this works, I'm going to build a simple "packet builder." Imagine we're writing a driver for a temperature sensor. The sensor expects a 4-byte packet: a start byte (0x02), a command byte, a data value, and a checksum.

Defining our packet structure

First, I'll start by creating the packet using the bytes type. I like using hex notation (0x..) here because it's the industry standard for binary data; seeing 165 is far less intuitive than seeing 0xA5.

# Let's define a basic "Read Temperature" packet
# Start byte: 0x02, Command: 0x10, Value: 0x00, Checksum: 0x00
packet = bytes([0x02, 0x10, 0x00, 0x00])
print(f"Initial packet: {packet}") 
# Output: b'\x02\x10\x00\x00'

Notice the b prefix in the output. That's Python telling you this isn't a string; it's a bytes object. Each element is an integer from 0 to 255.

The immutable wall

Now, here is where I usually trip up when I'm rushing a project. I need to update the third byte to reflect the actual temperature value (let's say 25 degrees Celsius, which is 0x19 in hex) and then calculate a simple checksum.

# I want to update the value to 25 (0x19)
packet[2] = 0x19 

If you run that, Python will throw a TypeError: 'bytes' object does not support item assignment. I forgot that bytes objects are immutable, just like strings. Once I've created that packet, I can't change a single bit of it. If I want to modify the data, I have two choices: create a brand new bytes object through concatenation, or use a bytearray.

Switching to bytearray for flexibility

Since we're building a packet that needs several updates before it's "ready," a bytearray is the right tool for the job. It's essentially a mutable version of bytes. Let's rewrite this logic.

# Convert our template to a bytearray
packet = bytearray([0x02, 0x10, 0x00, 0x00])

# Now we can actually change the values
packet[2] = 0x19  # Temperature value

# Let's calculate a primitive checksum: sum of first 3 bytes modulo 256
packet[3] = sum(packet[:3]) % 256

print(f"Updated packet: {packet}")
# Output: bytearray(b'\x02\x10\x19\x2b')

This is much cleaner. I can treat the bytearray like a list of integers, which makes the logic for calculating checksums or manipulating headers a lot easier to write and read.

Locking it down for transmission

Once the packet is constructed, you generally don't want it to be mutable anymore. If you're passing this packet to a network library or writing it to a file, those functions usually expect a bytes object. It's a safety measure—you don't want some other part of your program accidentally changing the checksum while the packet is in the send queue.

# Cast it back to immutable bytes for sending
final_packet = bytes(packet)

print(f"Ready to send: {final_packet}")
# Output: b'\x02\x10\x19\x2b'

And there you have it. Use bytearray when you're "assembling" or "editing" binary data, and cast it to bytes when it's time to ship it.




📋 Practical Task

Exercise: Building a XOR Binary Obfuscator

In some low-level protocols, data is "obfuscated" using a simple XOR operation to prevent plain-text reading. Your task is to write a function called obfuscate_data that takes a bytes object and a single-byte integer key.

  • The function should convert the bytes input into a bytearray.
  • Loop through every byte in the array and perform a XOR operation (using the ^ operator) with the provided key.
  • Return the final result as an immutable bytes object.

Test your code with this:

key = 0x42
original = b"Secret Message"
result = obfuscate_data(original, key)
print(result) # Should be a bytes object with altered values
print(obfuscate_data(result, key)) # XORing again with the same key should return the original message!
Rating
0 0

There are no comments for now.

to be the first to leave a comment.