Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes

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

Sponsored

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 with statement — it automatically calls f.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 filesPath.read_bytes() and Path.write_bytes() are the cleanest.
  • Large media files → chunked reading with 'rb' or shutil.copyfileobj().
  • Parsing binary protocolsstruct.unpack() from the struct module combined with chunked reads (covered in a later lesson).
  • Modifying in placebytearray over bytes.

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') causes UnicodeDecodeError or silently corrupts data.
  • Forgetting to encode a string to bytes before writing in binary mode: f.write('Hello') raises TypeError: 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

  1. Use pathlib.Path.read_bytes() and write_bytes() for one-shot binary I/O without needing the open() context manager syntax.
  2. Replace manual chunking with shutil.copyfileobj(src, dst) for simple binary file copies with automatic buffering.
  3. Use memoryview with bytearray for 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 .pkl file using the pickle module (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 bytes objects — not strings; convert with .encode() when needed.
  • Process large binary files in fixed-size chunks (e.g., 4 KB) to avoid memory exhaustion.
  • Use bytearray when you need to modify binary content in place; bytes is immutable.
  • The with statement guarantees the file is properly closed and flushed, even on errors.
  • Binary mode supports full random access with seek() and tell(), unlike text mode.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.