Python bytearray Type: Mutable Binary Data
python bytearray type: Learn how to use Python's bytearray type for mutable binary data: creation, mutation, conversions, performance, and common pitfalls.
The python bytearray type provides a mutable sequence of integers in the range 0–255. Unlike bytes, which is immutable, bytearray allows in-place modification of the underlying binary data. This makes it useful when you need to build or alter binary buffers without creating new objects on every change.
The bytearray Type and Its Core Behavior
A bytearray behaves like a list of small integers, but each element is constrained to a single byte. When you access an element, you get an int; when you slice, you get a new bytearray. This distinction matters for operations like comparison and concatenation.
ba = bytearray(b'hello') print(ba[0]) # 104 print(ba[0:2]) # bytearray(b'he')
The object itself is iterable, and you can pass it to functions that expect a bytes-like object. Many binary protocols and file APIs accept bytearray directly, so you rarely need to convert it to bytes unless you need an immutable representation.
Creating a bytearray
You can create a bytearray from a bytes literal, a string with an encoding, an iterable of integers, or a preallocated size.
# From bytes ba1 = bytearray(b'abc') # From a string with encoding ba2 = bytearray('abc', 'utf-8') # From an iterable of integers ba3 = bytearray([104, 101, 108, 108, 111]) # Preallocate 10 zero bytes ba4 = bytearray(10)
The last form is useful when you know the buffer size in advance and plan to fill it later. It avoids repeated resizing and is common in protocol parsing where you read chunks into a fixed buffer.
Mutating a bytearray
The defining feature of bytearray is in-place mutation. You can assign to an index, append, extend, and delete slices just like a list.
ba = bytearray(b'hello') ba[0] = 72 # ASCII 'H' ba.append(33) # '!' ba.extend(b' world') # append multiple bytes ba[1:3] = b'EL' # replace slice print(ba) # bytearray(b'HELlo world!')
Index assignment requires an integer between 0 and 255. Slice assignment accepts any bytes-like object, including another bytearray or a bytes object. This flexibility makes bytearray a convenient accumulator when building binary messages incrementally.
bytearray vs bytes: When to Use Which
The choice between bytearray and bytes comes down to mutability and hashability. bytes is immutable and hashable, so it can be used as a dictionary key or stored in a set. bytearray is mutable and unhashable, so it cannot be used directly in those contexts.
| Property | bytes | bytearray |
|---|---|---|
| Mutability | Immutable | Mutable |
| Hashable | Yes | No |
| Index access | Returns int | Returns int |
| Slice access | Returns bytes | Returns bytearray |
| Use case | Fixed binary data | Incremental construction |
Use bytes when you have a constant binary payload, such as a cryptographic key or a file signature. Use bytearray when you need to modify the data in place, especially in loops that would otherwise create many intermediate bytes objects.
Converting Between bytearray, bytes, and Strings
Conversion between bytearray and bytes is cheap because both share the same underlying buffer concept, though bytes is immutable. To get an immutable copy, use bytes(ba).
ba = bytearray(b'data') b = bytes(ba)
To convert to a string, decode using an encoding. The reverse requires encoding a string.
ba = bytearray(b'hello') text = ba.decode('utf-8') ba2 = bytearray('hello', 'utf-8')
You can also create a memoryview over a bytearray to avoid copying when passing it to APIs that support buffer protocol.
ba = bytearray(1024) view = memoryview(ba) # view[0] = 255 works and modifies ba
This is useful in high-performance scenarios where you want to avoid duplicating large buffers.
Performance and Memory Considerations
Because bytearray is mutable, it can be more memory-efficient than repeatedly concatenating bytes objects. Each concatenation of bytes creates a new object and copies the old data. With bytearray, you can extend in place, avoiding that copy when the underlying buffer has capacity.
However, bytearray is not a magic bullet. It has more overhead than bytes for read-only operations because it tracks mutability and may need to resize. If you never modify the data after creation, bytes is lighter and can be shared safely across threads.
For large binary data, consider using memoryview to avoid copies when slicing. A slice of a bytearray returns a new bytearray, which copies the data. A memoryview slice references the original buffer, reducing memory usage.
ba = bytearray(b'0123456789') view = memoryview(ba) sub = view[2:5] # references ba, no copy sub[0] = 88 # modifies ba print(ba) # bytearray(b'0183456789')
This behavior is critical when processing large binary files or network packets where copying would dominate runtime.
Common Pitfalls and Edge Cases
One frequent mistake is assuming that indexing returns a bytes object. It returns an int, so comparing it to a bytes literal fails.
ba = bytearray(b'abc') if ba[0] == b'a': # False, because b'a' is bytes pass # Correct: compare to integer if ba[0] == 97: pass ```n Another pitfall is using `bytearray` as a dictionary key. Since it is mutable, it cannot be hashed. If you need a mutable buffer that also serves as a key, you must convert it to `bytes` first, which creates a copy. When preallocating with `bytearray(n)`, all elements are initialized to zero. This is fine for many use cases, but if you plan to fill the entire buffer, you might not need the zero-initialization overhead. In CPython, the allocation is fast, but for very large buffers, the zero fill takes time. Finally, be careful when passing a `bytearray` to a function that expects a read-only bytes-like object. Most standard library functions accept it, but some C extensions may require `bytes` explicitly. In those cases, a conversion is necessary, and you should weigh the cost of that copy against the benefits of mutability. For protocol handling, a common pattern is to read data into a `bytearray`, parse it, and then extract immutable `bytes` slices for hashing or storing. This combines the efficiency of in-place parsing with the safety of immutable keys.