Reading & Writing Binary Files
Learn how to read and write binary files in Python with clear, hands-on steps. This practical tutorial covers core concepts, step-by-step examples, common edge cases, and what to study next.
Focus: read and write binary files in python
When you open an image, execute a compiled .exe, or send data over a network socket, you are not working with plain text — you are handling raw bytes. Text mode ('r' or 'w'), which automatically decodes and encodes strings, will corrupt non-text data like images, audio, or ZIP archives. This lesson teaches you exactly how to read and write binary files in Python without data loss or corruption, using the 'rb' and 'wb' modes and the bytes type.
The problem this lesson solves
Text files are convenient, but many real-world files are binary: images, executables, compressed archives, serialised objects, and proprietary formats. If you try to open a JPEG image with open('photo.jpg', 'r'), Python will attempt to decode its bytes as UTF-8 (or another encoding), raising a UnicodeDecodeError and rendering the file unusable. Even when no error is raised, the decoded content is often corrupted because binary data is not valid text.
Similarly, writing binary data in text mode ('w') will try to encode your bytes object as text, which can fail with a TypeError or silently change the data. The core problem is that text mode treats the file as a sequence of characters, while binary mode treats it as a sequence of bytes — two fundamentally different abstractions.
Core concept / mental model
Think of a file as a long sequence of numbers, each between 0 and 255 (one byte). In text mode, Python interprets these numbers as characters using an encoding (like UTF-8), grouping bytes to form Unicode code points. In binary mode, Python gives you direct access to those raw numbers — no interpretation, no grouping.
| File mode | What you read and write | Data type | When to use |
|---|---|---|---|
'r' / 'w' |
Text characters | str |
Text files (.txt, .csv, .json, .html) |
'rb' / 'wb' |
Raw bytes | bytes |
Binary files (.jpg, .exe, .zip, .mp3, .pkl) |
'ab' |
Append raw bytes | bytes |
Logs, incremental binary writes |
Analogy: Text mode is like a translator who reads a Spanish book aloud in English. Binary mode is like handling the original printed pages — you see every ink spot as it is.
How it works step by step
Step 1: Opening a file in binary mode
Use the built-in open() function with 'rb' (read binary) or 'wb' (write binary). The file is opened as a BufferedReader or BufferedWriter, which provides efficient byte-level I/O.
# Read an existing binary file
with open('data.bin', 'rb') as f:
raw_bytes = f.read()
print(type(raw_bytes)) # <class 'bytes'>
print(len(raw_bytes)) # Number of bytes read
Step 2: Reading specific numbers of bytes
Binary files can be huge. Use f.read(n) to read precisely n bytes at a time.
with open('image.jpg', 'rb') as f:
header = f.read(4) # First 4 bytes (file signature)
rest = f.read(1024) # Next 1024 bytes
print(header.hex()) # 'ffd8ffe0' for JPEG
Step 3: Writing bytes to a binary file
You must pass a bytes object. Convert strings using .encode() or use bytes() for raw integers.
# Create binary content manually
bytes_content = bytes([0x48, 0x65, 0x6C, 0x6C, 0x6F]) # 'Hello' in hex
with open('output.bin', 'wb') as f:
f.write(bytes_content)
# Verify: read back
with open('output.bin', 'rb') as f:
print(f.read()) # b'Hello'
Pro tip: Always use the
withstatement — it automatically callsf.close()even if an error occurs, ensuring all buffered bytes are flushed to disk.
Step 4: Appending bytes
To add data to an existing binary file without overwriting, use 'ab'.
with open('log.bin', 'ab') as f:
f.write(b'\x01\x02\x03')
Hands-on walkthrough
Example 1: Copy a binary file chunk by chunk
Large binary files should not be loaded entirely into memory. Here's a reliable copy routine:
CHUNK_SIZE = 4096 # 4 KB
with open('source.zip', 'rb') as src, open('copy.zip', 'wb') as dst:
while True:
chunk = src.read(CHUNK_SIZE)
if not chunk:
break
dst.write(chunk)
print('Binary file copied successfully.')
# Output: Binary file copied successfully.
Expected output: The file copy.zip is identical to source.zip (hash-check: md5sum source.zip copy.zip).
Example 2: Read and interpret a PNG file header
PNG files start with an 8-byte signature. Let's verify it:
png_signature = b'\x89PNG\r\n\x1a\n'
with open('example.png', 'rb') as f:
header = f.read(8)
if header == png_signature:
print('Valid PNG file')
else:
print('Not a PNG file')
# Output: Valid PNG file (if example.png is a real PNG)
Example 3: Convert between bytearray and bytes for modification
bytearray is a mutable counterpart to bytes. Use it to modify binary data in place:
with open('data.bin', 'rb') as f:
data = bytearray(f.read()) # mutable bytes
# Modify first 4 bytes
for i in range(4):
data[i] = 0xFF
with open('data_mod.bin', 'wb') as f:
f.write(data)
Compare options / when to choose what
| Approach | Best for | Caveat |
|---|---|---|
open(file, 'rb') + .read() |
Small files (<1 GB) | Loads entire file into memory — use chunks for larger files |
open(file, 'rb') + loop with .read(CHUNK_SIZE) |
Large files, streaming | Slightly more code |
shutil.copyfileobj() |
Copying files efficiently | Reads and writes using optimal buffer size internally |
pathlib.Path.read_bytes() / write_bytes() |
Simple one-shot reads/writes | No incremental processing |
memoryview + cast |
High-performance numeric arrays | Advanced; requires understanding of C-level layout |
When to choose what:
- Small configuration binary files →
Path.read_bytes()andPath.write_bytes()are the cleanest. - Large media files → chunked reading with
'rb'orshutil.copyfileobj(). - Parsing binary protocols →
struct.unpack()from thestructmodule combined with chunked reads (covered in a later lesson). - Modifying in place →
bytearrayoverbytes.
Troubleshooting & edge cases
Mistake 1: Opening binary file in text mode
# Wrong: will cause UnicodeDecodeError for non-text files
try:
with open('image.jpg', 'r') as f:
data = f.read()
except UnicodeDecodeError as e:
print('Error:', e)
# Output: Error: 'utf-8' codec can't decode byte 0xff in position 0
Mistake 2: Forgetting to encode strings when writing in binary mode
with open('out.bin', 'wb') as f:
# f.write('Hello') # TypeError: a bytes-like object is required, not 'str'
f.write('Hello'.encode('utf-8')) # Correct
Mistake 3: Not flushing or closing properly (rare with with, but manual close can lose data)
f = open('data.bin', 'wb')
f.write(b'important')
# Oops — no f.close() — data may be lost
Always use with open(...) as f:.
Edge case: Reading an empty binary file
with open('empty.bin', 'rb') as f:
data = f.read()
print(len(data)) # 0
print(bool(data)) # False — careful with `if data: ...`
Edge case: seek() and tell() with binary files
Binary files support random access via seek(offset, whence), where whence is 0 (start), 1 (current), or 2 (end).
with open('data.bin', 'rb') as f:
f.seek(10, 0) # Move to byte 10 from start
chunk = f.read(4) # Read 4 bytes
print(f.tell()) # 14
Pro tip: In text mode,
seek()is limited because of variable-width encodings like UTF-8. Binary mode gives you full random access.
What you learned & what's next
You now understand:
- Why binary mode ('rb', 'wb', 'ab') is essential for non-text files.
- How to read and write raw bytes using bytes and bytearray.
- How to process large binary files in chunks to avoid memory overflow.
- How to distinguish between text and binary file handling in Python.
- Common pitfalls: UnicodeDecodeError, forgetting .encode(), and manual file closure.
Next step: In the next lesson, you will learn how to parse structured binary data using the struct module — turning raw bytes into integers, floats, and strings for protocols and file formats. This is the foundation for building decoders for PNG, WAV, ZIP, or custom binary formats.
Keep your bytes raw, your writes safe, and your with statements always at your side.
Practice recap
Create a small Python script that opens a .png file (or any binary file), reads its first 8 bytes, and prints them in hexadecimal. Then, write a 512-byte buffer of all zeros to a new binary file zeros.bin and confirm its size with os.path.getsize(). This will cement your understanding of reading and writing raw bytes.
Common mistakes
- Opening a binary file in text mode (
'r'instead of'rb') causesUnicodeDecodeErroror silently corrupts data. - Forgetting to encode a string to
bytesbefore writing in binary mode:f.write('Hello')raisesTypeError: a bytes-like object is required, not 'str'. - Not handling large files in chunks — calling
f.read()on a 2 GB file will consume all available memory and may crash your program. - Assuming
f.tell()returns a character offset in text mode — always use'rb'for reliable byte positions.
Variations
- Use
pathlib.Path.read_bytes()andwrite_bytes()for one-shot binary I/O without needing theopen()context manager syntax. - Replace manual chunking with
shutil.copyfileobj(src, dst)for simple binary file copies with automatic buffering. - Use
memoryviewwithbytearrayfor memory-efficient, zero-copy modifications of large binary buffers.
Real-world use cases
- Reading a JPEG photo from disk to compute a hash or verify its header signature for image validation.
- Writing a serialised Python object to a
.pklfile using thepicklemodule (which always operates in binary mode). - Downloading a ZIP archive from an API and writing it to disk byte-by-byte to preserve compression integrity.
Key takeaways
- Always open non-text files with
'rb'(read) or'wb'(write) to avoid encoding errors and data corruption. - Binary mode returns/expects
bytesobjects — not strings; convert with.encode()when needed. - Process large binary files in fixed-size chunks (e.g., 4 KB) to avoid memory exhaustion.
- Use
bytearraywhen you need to modify binary content in place;bytesis immutable. - The
withstatement guarantees the file is properly closed and flushed, even on errors. - Binary mode supports full random access with
seek()andtell(), unlike text mode.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.