Exceptions and Context Managers
Python try/except vs C error codes, context managers vs manual cleanup
Introduction
In this lesson, you'll learn about exceptions and context managers in Python. Coming from C, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.
In C, you're familiar with python try/except vs c error codes, context managers vs manual cleanup.
Python has its own approach to python try/except vs c error codes, context managers vs manual cleanup, which we'll explore step by step.
The Python Way
Let's see how Python handles this concept. Here's a typical example:
# Python: exceptions propagate automatically
# try / except / finally
def process_file(path: str) -> str:
try:
with open(path, "r") as f: # with auto-closes on exit
data = f.read()
return process(data)
except FileNotFoundError:
print(f"File not found: {path}")
return ""
except PermissionError as e:
raise RuntimeError(f"Cannot read {path}") from e
# Custom context manager with __enter__/__exit__
class Timer:
def __enter__(self):
import time
self.start = time.perf_counter()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.elapsed = time.perf_counter() - self.start
return False # don't suppress exceptions
with Timer() as t:
result = expensive_operation()
print(f"Took {t.elapsed:.3f}s")
# contextlib.contextmanager (simpler)
from contextlib import contextmanager
@contextmanager
def managed_resource():
resource = acquire()
try:
yield resource
finally:
release(resource) # always runs
with managed_resource() as r:
use(r)Comparing to C
Here's how you might have written similar code in C:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
// C: error codes returned or global errno
int process_file(const char *path) {
FILE *f = fopen(path, "r");
if (f == NULL) {
fprintf(stderr, "fopen: %s\n", strerror(errno));
return -1; // error code
}
int *buf = malloc(1024);
if (buf == NULL) {
fclose(f); // must remember to clean up!
return -1;
}
// ... process ...
if (error_condition) {
free(buf);
fclose(f); // easy to forget cleanup
return -1;
}
free(buf);
fclose(f);
return 0; // success
}You may be used to different syntax or behavior.
Python exceptions propagate automatically — no error code checks needed
You may be used to different syntax or behavior.
with statement auto-calls __exit__ even on exception — replaces C's manual cleanup
You may be used to different syntax or behavior.
FileNotFoundError, PermissionError are descriptive — errno requires strerror()
You may be used to different syntax or behavior.
Custom context managers via __enter__/__exit__ or @contextmanager decorator
You may be used to different syntax or behavior.
No goto needed — finally/with handle cleanup at any exception point
Step-by-Step Breakdown
1. Exception vs Error Code
Python exceptions propagate up the call stack automatically. In C, error codes must be checked and propagated manually at every call site.
int r = do_thing();
if (r < 0) { cleanup(); return r; }result = do_thing() # exception propagates automatically
# cleanup handled by with statement or finally2. with Statement
The 'with' statement calls __exit__ when the block exits — even if an exception occurs. Replaces C's manual cleanup pattern.
FILE *f = fopen(...);
if (!f) return -1;
// ... process ...
fclose(f); // must remember!with open(path, "r") as f:
data = f.read()
# f.close() called automatically — even on exception3. Custom Context Managers
Any class with __enter__ and __exit__ works in a 'with' statement. Use @contextmanager for simpler cases.
// C: wrapper function with init/cleanup
resource_t *acquire();
void release(resource_t *r);@contextmanager
def db_transaction(conn):
try:
yield conn
conn.commit()
except Exception:
conn.rollback()
raise
with db_transaction(conn) as c:
c.execute("INSERT ...")4. Exception Hierarchy
Python's exception hierarchy mirrors OS errors. Catch specific types first, then broader types.
if (errno == ENOENT) { ... }
else if (errno == EACCES) { ... }try:
open(path)
except FileNotFoundError: # ENOENT equivalent
print("not found")
except PermissionError: # EACCES equivalent
print("permission denied")
except OSError as e: # any OS error
print(f"OS error: {e.strerror}")Common Mistakes
When coming from C, developers often make these mistakes:
- Python exceptions propagate automatically — no error code checks needed
- with statement auto-calls __exit__ even on exception — replaces C's manual cleanup
- FileNotFoundError, PermissionError are descriptive — errno requires strerror()
Key Takeaways
- Exceptions propagate automatically — no error code checks at every call site
- with statement = auto-cleanup (like try/finally with fclose); works for any context manager
- FileNotFoundError/PermissionError are descriptive; IOError/OSError for generic OS errors
- @contextmanager decorator turns a generator into a context manager