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
95: Context Managers and the with Statement
I've noticed a pattern with developers moving into intermediate Python: they start using the with statement because they're told it's "best practice" for opening files, but they view it as nothing more than a syntactic shortcut for a try...finally block. They think, "I'm just using this so I don't have to remember to call file.close()."
Thinking with is just a shortcut for closing files
If you think of with as just a "file closer," you're missing the actual power of the Context Manager protocol. Let's look at the "manual" way you might handle a resource if you didn't have with:
f = open("data.txt", "w")
try:
f.write("Hello world")
# Imagine some complex logic here that might crash
finally:
f.close()
Now, here is the with version:
with open("data.txt", "w") as f:
f.write("Hello world")
On the surface, they do the same thing. But the with statement isn't actually "about" files. It's about scope management. It defines a clear boundary where a resource is active and ensures that, no matter how you exit that boundary—whether the code finishes successfully or crashes with a catastrophic error—the cleanup logic is guaranteed to run. This is a fundamental design pattern in Python called a Context Manager.
Seeing the with statement as a resource lifecycle protocol
The real magic happens when you realize you can make any class a context manager by implementing two magic methods: __enter__ and __exit__. This allows you to manage things that have nothing to do with files, like database connections, network sockets, or even timing how long a piece of code takes to run.
I personally love using context managers for things like temporary state changes. Imagine you have a system where you need to temporarily change the precision of a calculation or a global configuration setting, and then revert it back to the original value regardless of whether the calculation succeeded.
class TemporarySettings:
def __init__(self, key, new_value):
self.key = key
self.new_value = new_value
self.old_value = None
def __enter__(self):
# Setup: Save the old state and apply the new one
print(f"Saving old setting for {self.key}...")
self.old_value = settings.get(self.key)
settings[self.key] = self.new_value
return self # This is what 'as' captures
def __exit__(self, exc_type, exc_val, exc_tb):
# Teardown: Restore the old state
print(f"Restoring setting for {self.key}...")
settings[self.key] = self.old_value
# Returning False allows any exceptions to propagate normally
return False
# Usage:
with TemporarySettings("debug_mode", True):
print("System is now in debug mode")
# If an error happens here, __exit__ still runs!
# do_something_risky()
print("System is back to normal")
Notice the arguments in __exit__. Python passes in the exception type, value, and traceback if an error occurred inside the with block. This gives you incredible control. You can actually suppress specific errors by returning True from __exit__, though I'd warn you to use that sparingly—swallowing exceptions is a great way to hide bugs from yourself.
When you're designing your own systems, ask yourself: "Do I have a piece of setup that must have a corresponding teardown?" If the answer is yes, don't rely on the user to remember to call a .stop() or .cleanup() method. Wrap it in a context manager. It makes your API cleaner and your code significantly more robust.
📋 Practical Task
Building a Database Transaction Simulator
In this exercise, you will create a context manager that simulates a database transaction. In a real database, a transaction ensures that a series of changes are either all committed (saved) or all rolled back (undone) if an error occurs.
Requirements:
- Create a class named
DatabaseTransaction. - The
__enter__method should print"BEGIN TRANSACTION"and return a mock connection object (a simple string like"DB_CONNECTION"will do). - The
__exit__method must check if an exception occurred:- If no exception occurred, print
"COMMIT TRANSACTION". - If an exception occurred, print
"ROLLBACK TRANSACTION".
- If no exception occurred, print
- Ensure that the exception is still raised after the rollback (do not suppress it).
Test your implementation with these two scenarios:
# Scenario 1: Success
with DatabaseTransaction() as conn:
print(f"Using {conn} to update records...")
# No error here
# Scenario 2: Failure
try:
with DatabaseTransaction() as conn:
print(f"Using {conn} to update records...")
raise RuntimeError("Database connection lost!")
except RuntimeError:
print("Caught the error in main loop")There are no comments for now.