PYTHON / CAPSTONE PROJECTS
Where to go next as a Python developer
Audit which parts of Python you actually use, choose your next area from evidence rather than hype, and turn working scripts into maintained code.
What you will learn
- Scan your own code with ast to list which modules you really import
- Split imports into stdlib and third-party using sys.stdlib_module_names
- Learn an unfamiliar API from the REPL with inspect.signature and dir
- Choose the next topic from a real failure you hit, not from a trend list
Understanding Where to go next as a Python developer
After the capstones, syntax is no longer the thing holding you back. What separates you from a working Python engineer is judgement: reading code you did not write, measuring instead of assuming, and running code in an environment you do not control. The five projects in this section each gave you one slice of that (a CLI, filesystem work, data plotting, a database-backed web app, a model with an evaluation), and the natural next move is to pick one of those slices and take it much deeper rather than starting a sixth shallow project.
The most reliable way to choose that direction is to look at what your own code already contains. A Python file is itself parseable Python data: the ast module gives you the import statements without executing anything, and sys.stdlib_module_names (Python 3.10+) tells you which of those names ship with the interpreter. Run that over your projects and you usually find the same twelve modules over and over, which means the gaps are visible and concrete: no logging, no dataclasses, no unittest, no argparse, no sqlite3.
From there the depth directions are specific, not vague. Packaging and environments (venv, pyproject.toml, pinned versions) explains why your script runs for you and not for a colleague. Typing plus mypy turns runtime AttributeError into an error you see before running. dis and the data model explain why some loops are slow and why mutable default arguments bite. concurrency (threads for I/O, processes or async for the rest) explains why adding threads to a CPU-bound loop made it slower. Pick whichever one matches a failure you actually hit; that failure is the reason the material will stick.
Finally, shift from consuming lessons to maintaining something. A project you return to for six months forces you to write tests you will trust, read tracebacks from code you forgot, and read the source of libraries you depend on, which is where most real Python learning happens.
import ast
import sys
source = '''
import csv, json
from pathlib import Path
import pandas as pd
from django.db import models
import concurrent.futures
'''
def imported_roots(code):
roots = set()
for node in ast.walk(ast.parse(code)):
if isinstance(node, ast.Import):
for alias in node.names:
roots.add(alias.name.split(".")[0])
elif isinstance(node, ast.ImportFrom) and node.level == 0:
roots.add(node.module.split(".")[0])
return roots
NEXT = ["argparse", "dataclasses", "functools", "itertools",
"logging", "sqlite3", "unittest"]
roots = imported_roots(source)
print("stdlib:", sorted(r for r in roots if r in sys.stdlib_module_names))
print("external:", sorted(r for r in roots if r not in sys.stdlib_module_names))
print("next up:", [m for m in NEXT if m not in roots])Your next step should be chosen from evidence in your own code and your own failures, not from a list of trending topics.
Worked examples
Mining one unfamiliar stdlib module
Two itertools functions replace loops most people write by hand, which is what reading one module per week buys you.
from itertools import accumulate, groupby
log = ["ok", "ok", "fail", "fail", "fail", "ok"]
print([(key, len(list(group))) for key, group in groupby(log)])
print(list(accumulate([3, 1, 4, 1, 5], max)))Example explained
Line 1groupby collapses only adjacent equal values, so it reports three runs rather than two distinct labels.
Line 2len(list(group)) consumes the group iterator; touching it after moving on to the next key yields nothing.
Line 3accumulate takes any two-argument function, so passing max gives a running maximum instead of a running sum.
Line 4Both replace a manual counter-and-previous-value loop, which is the kind of code you stop writing once you read the module.
Learning an unfamiliar API from the REPL
inspect answers questions about a callable's parameters without opening a browser tab.
import inspect
def fetch(url, *, retries=3, timeout=5.0):
return url, retries, timeout
sig = inspect.signature(fetch)
print(sig)
print(list(sig.parameters))
print(sig.parameters["timeout"].default)
print(dict(sig.bind("http://example.test").arguments))Example explained
Line 1inspect.signature(fetch) prints the same shape as the def line, including the bare * that forces keyword-only arguments.
Line 2sig.parameters is a mapping, so list() gives the names in declaration order.
Line 3The default is returned as the real object (5.0, a float), not as text.
Line 4sig.bind only records arguments you actually passed, so defaults are absent until you call apply_defaults().
Important notes
sys.stdlib_module_names exists from Python 3.10 onward; on older versions there is no reliable built-in list of stdlib names.
An ast import scan sees only static imports, so it misses importlib.import_module calls and anything imported inside a function or a try/except fallback.
Common mistakes
Starting a new beginner course in another language or framework instead of maintaining one Python project, so you never meet the problems (dependency drift, regressions, unreadable old code) that the next tier of Python knowledge exists to solve.
Installing packages system-wide with sudo pip, which can overwrite versions the operating system depends on and makes it impossible to tell which of your projects needs what.
Repeating performance folklore such as 'comprehensions are always faster' without ever running timeit, then rewriting readable code for a gain you cannot demonstrate.
Try it yourself
Change, predict, then run
Paste one of your capstone scripts into the source string in the main example and run it, then name the single stdlib module from the 'next up' list that would most improve that specific script and say why in one sentence.
Open the Python workspaceCheck your understanding
Your data-cleaning script runs on your machine but crashes on a colleague's with an ImportError. Which next step addresses the actual cause?
- Create a venv and pin the project's dependencies in a requirements or pyproject file
- Add type hints and run mypy over the project
- Rewrite the file-reading parts with asyncio
- Switch to a faster Python interpreter build
Show answer
An ImportError on another machine means the environment differs, and only recording and reproducing the exact dependencies fixes that. Type hints are tempting because they catch real bugs, but mypy checks your source, not which packages happen to be installed elsewhere, so the same import would still fail.