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
139: Exceptions and the Try/Except Block
I've noticed a lot of developers coming from C++ or Java carry a habit into Python that I call "defensive hoarding." They spend half their function writing checks to make sure nothing goes wrong before they actually perform the operation. In the industry, we call this LBYL: Look Before You Leap. It seems intuitive, but in Python, it often leads to brittle code that misses edge cases.
The "Look Before You Leap" Trap
Let's say you're writing a piece of code that takes a user's input—maybe a character's level for a game—and converts it to an integer. A naive approach looks like this:
def set_character_level(user_input):
if user_input.isdigit():
level = int(user_input)
print(f"Level set to {level}")
else:
print("That's not a valid number!")
At first glance, this looks safe. You checked if it was a digit, so int() won't crash, right? Not exactly. What happens if the user enters "-5"? isdigit() returns False because the minus sign isn't a digit, even though int("-5") is perfectly valid Python. Or what if there's a trailing space? You end up writing a mountain of if/elif statements just to validate a single piece of data. You're trying to predict every possible way the input could be "wrong," which is a losing battle.
Embracing the Crash with Try/Except
The Pythonic way is to embrace a philosophy called EAFP: Easier to Ask Forgiveness than Permission. Instead of trying to predict the failure, we just try the operation and handle the fallout if it blows up. This is where the try/except block comes in.
def set_character_level(user_input):
try:
level = int(user_input)
print(f"Level set to {level}")
except ValueError:
print("That's not a valid number!")
Notice how much cleaner this is. I'm not guessing if the string is "digit-like." I'm telling Python: "Try to make this an integer. If it's impossible, Python will throw a ValueError. When that happens, stop panicking and run this recovery code instead." By catching the specific ValueError, we handle the failure exactly when and where it happens, without worrying about minus signs or weird whitespace.
The Trade-off: Precision vs. Prediction
You might wonder if this is "sloppy" because we're letting an error happen. In reality, it's more precise. When you use isdigit(), you're creating your own arbitrary rule for what a "number" is. When you use try/except, you're relying on the language's own internal logic for what constitutes a valid integer.
One word of warning, though: never use a "bare" except. I've seen juniors do this:
try:
do_something()
except:
print("Something went wrong")
This is a nightmare to debug. A bare except catches everything, including KeyboardInterrupt (when you try to hit Ctrl+C to stop the program) or MemoryError. You'll find yourself staring at a "Something went wrong" message while your program is actually crashing because of a typo in a variable name (a NameError) that you accidentally suppressed. Always name the specific exception you expect—like ValueError, TypeError, or FileNotFoundError—so you don't accidentally hide bugs that you actually need to see.
📋 Practical Task
Sanitizing the Corrupted High Score File
You've been handed a text file called scores.txt that contains a list of high scores, one per line. However, the file is corrupted; some lines contain actual numbers, while others contain random text fragments or empty lines.
Write a script that does the following:
- Reads each line of the file
scores.txt. - Attempts to convert the line to an integer.
- If successful, adds the score to a list called
valid_scores. - If a
ValueErroroccurs, prints a message saying "Skipping invalid entry: [the value]" and continues to the next line without crashing. - Finally, prints the sum of all
valid_scores.
Setup: Before running your code, create a file named scores.txt with the following content:
100
250
corrupted_data
500
broken_score
1000There are no comments for now.