Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
94: Reading and Writing Binary Files
Up until now, we've mostly dealt with text files. Text files are great because you can open them in Notepad or VS Code and actually read what's inside. But in the professional world, you'll often encounter binary files—things like images, compiled bytecode, or custom save-game formats. These aren't meant for humans; they're meant for the machine.
The biggest difference is that while text files handle strings, binary files handle bytes. If you try to treat a binary file like a text file, you'll end up with a mess of weird characters and likely a crashed program. Today, I want to show you how to actually structure data for a binary file using a small example: a player profile for a game.
Defining our binary structure
When we write to a binary file, we can't just "print" variables. We have to decide exactly how many bytes each piece of data takes up. For this, I'm going to use the struct module. It's a bit old-school, but it's the standard way to pack Python values into C-style bytes.
I want to save a player's health (an integer) and their level (an integer). In struct language, an integer is represented by 'i'. So, two integers would be 'ii'.
import struct
health = 85
level = 12
# 'ii' means two integers
binary_data = struct.pack('ii', health, level)
print(binary_data)
# Output looks like: b'U\x05\x00\x00\x0c\x00\x00\x00'
The "TypeError" I always hit
Now, I want to add the player's name to this file. My first instinct is usually to just pack the name along with the numbers. I'll try to write it all to a file using 'wb' (write binary) mode.
player_name = "Aragorn"
with open("player.dat", "wb") as f:
f.write(binary_data)
f.write(player_name) # I'm making a mistake here...
If you run this, Python will scream at you: TypeError: a bytes-like object is required, not 'str'. I've lost count of how many times I've forgotten this. In binary mode, Python refuses to guess how to turn your string into bytes. You have to be explicit. I need to encode() the string first.
Here is the corrected version:
player_name = "Aragorn"
with open("player.dat", "wb") as f:
f.write(binary_data)
f.write(player_name.encode('utf-8'))
Getting the data back out
Reading binary files is where things get tricky because the file doesn't tell you where one piece of data ends and the next begins. You have to read the exact number of bytes you wrote.
Since I used 'ii', and a standard integer is 4 bytes, I know the first 8 bytes are my stats. Everything after that is the name.
with open("player.dat", "rb") as f:
# Read the first 8 bytes for health and level
stats_bytes = f.read(8)
health, level = struct.unpack('ii', stats_bytes)
# Read the rest of the file for the name
name_bytes = f.read()
player_name = name_bytes.decode('utf-8')
print(f"Player: {player_name}, HP: {health}, Lvl: {level}")
It's a bit more manual than using JSON or a database, but it's incredibly fast and results in much smaller files. Just remember: when you're in 'rb' or 'wb' mode, everything must be bytes. No exceptions.
📋 Practical Task
Exercise: Building a Binary High-Score Table
Your task is to create a program that saves a list of high scores to a binary file. Each entry should consist of a score (an integer) and a player ID (an integer).
- Create a list of tuples containing at least three scores, e.g.,
[(1500, 1), (1200, 2), (900, 3)]. - Write these to a file named
scores.binusing thestructmodule. Use the format'ii'for each pair. - Write a separate function to read the
scores.binfile and print the scores back out as a list of tuples. - Challenge: Ensure your reading loop continues until the end of the file is reached, since you won't know how many scores are in the file beforehand.
There are no comments for now.