I’ve been shipping code in JavaScript, TypeScript, and Go for years. Python was the gap. I went through Python Crash Course by Eric Matthes from start to finish, and it’s one of the best resources I’ve used to go from zero to “I can build and ship Python.” Clear structure, no fluff, and exercises that force you to type code instead of skim. Once you finish it, you have enough to write scripts, small tools, and read production Python with confidence.
Here’s what the book gives you by the end, one thing it doesn’t emphasize enough (virtual environments), and something a bit more advanced: writing your own context manager so you can use with for your own resources.
After finishing the book
By the time you finish Python Crash Course, you’ve got variables, strings, numbers, lists, conditionals, loops, dictionaries, user input, while, functions, classes, files, and at least one project (game, data viz, or web app). You get f-strings, list comprehensions, .get() and .items(), and the habit of “run it, break it, fix it.” The exercises are the right mix of guided and open-ended. Doing every one is what makes it stick.
One pattern that pays off everywhere: safe dict access. In real code you often don’t know if a key exists. Using dict.get(key, default) instead of dict[key] avoids KeyError. Wrapping that in a small helper for nested keys keeps scripts from blowing up on missing data—more on that below.
What you need in the real world: virtual environments
The book gets you writing Python; it doesn’t spend much time on where you run it. In practice, you never install packages globally. You use an isolated environment per project. That’s what venv (built into Python) is for.
Why venv matters
- Dependencies: Project A needs
requests==2.28, project B needs2.31. One global install can’t satisfy both. - Reproducibility:
pip freeze > requirements.txtandpip install -r requirements.txtso you and others can recreate the same environment. - No pollution: Your system Python stays clean; no
sudo pip installor permission mess.
Quick loop
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
# ... work ...
pip freeze > requirements.txt
deactivate
Create → activate → install → freeze when deps change → deactivate. Once that’s muscle memory, you’re set.
Safe nested access: a small helper
Here’s a minimal pattern for reading nested dict keys (e.g. from JSON or API responses) without crashing on missing keys:
import json
def get_nested(data: dict, *keys, default=None):
"""Get a nested key without KeyError. Example: get_nested(d, 'user', 'address', 'city')"""
for key in keys:
if not isinstance(data, dict):
return default
data = data.get(key)
if data is None:
return default
return data
with open("config.json") as f:
config = json.load(f)
city = get_nested(config, "user", "address", "city", default="unknown")
print(f"City: {city}")
data.get(key) returns None or your default if the key is missing. Chaining that in a loop gives you safe deep access. Type hint data: dict helps when the script grows.
Super technical: writing your own context manager
The book shows with open(...) as f. You use with so the file is closed even if an exception happens. That’s a context manager. Once you know how they work, you can write your own for any “setup → use → teardown” pattern: locks, timers, temporary state, API sessions, etc.
Python gives you two ways to implement one.
1. Class-based: __enter__ and __exit__
Any class that defines __enter__ and __exit__ can be used with with:
import time
class Timer:
"""Context manager that prints how long the block took."""
def __enter__(self):
self.start = time.perf_counter()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
elapsed = time.perf_counter() - self.start
print(f"Elapsed: {elapsed:.3f}s")
return False # don't suppress exceptions
# Usage
with Timer():
time.sleep(0.1)
# Elapsed: 0.100s
__enter__runs when you enter thewithblock; its return value is what you get afteras(e.g.with Timer() as t:).__exit__runs when you leave the block (normally or by exception). The three arguments are the exception type, value, and traceback; if you returnTrue, Python swallows the exception. ReturningFalse(or nothing) re-raises it.- Here we only use it to measure time and print; we don’t suppress errors.
2. Generator-based: @contextmanager
For simple cases, a generator plus contextlib.contextmanager is less boilerplate:
from contextlib import contextmanager
@contextmanager
def temporary_override(obj, attr, value):
"""Temporarily override an attribute; restore it when leaving the block."""
old = getattr(obj, attr, None)
setattr(obj, attr, value)
try:
yield
finally:
if old is not None:
setattr(obj, attr, old)
else:
delattr(obj, attr)
# Example: temporarily change a config
class Config:
debug = False
with temporary_override(Config, "debug", True):
print(Config.debug) # True
print(Config.debug) # False again
- Everything before
yieldis “setup”; everything after (in thefinally) is “teardown”. - The
finallyensures teardown runs even if the block raises. That’s what makes context managers safe. - This pattern shows up everywhere: mocking in tests, changing env vars, acquiring and releasing locks.
Why this is worth knowing
Once you can write a context manager, you stop repeating try/finally by hand for the same resource. You get one place that owns “open/close” or “acquire/release,” and callers just write with MyResource():. It’s the same idea as with open(...) but for your own abstractions. The book doesn’t go deep here; learning it yourself is a solid next step after finishing the last chapter.
What’s next
After finishing Python Crash Course you have enough to write CLI tools, automate tasks, and read other people’s Python. Adopt venv and safe dict access early; add context managers when you start building things that manage resources (files, connections, locks). If you’re going through the book or coming from another language, those three will make your code feel more professional and less “tutorial-only.”