Python modules are files; packages are directories with init.py (or namespace packages). Learn imports before jumping to FastAPI.
Core concepts
- Functions:
def, return values, default args — avoid mutable defaults (def f(x=[])bug). — Python docs: Defining functions - ***args / kwargs: Variadic parameters for wrappers and decorators. — Python docs: Arbitrary argument lists
- Modules:
import jsonorfrom pathlib import Path. — Python docs: Modules - Packages & pip: Install from PyPI; lock deps for reproducible deploys. — Python packaging: installing packages
- name == "main": Entry point guard for scripts vs imports. — Python docs: main
def retry(fn, attempts=3):
last = None
for _ in range(attempts):
try:
return fn()
except Exception as exc:
last = exc
raise last
Concept videos
YouTube lessons for these topics. On a large screen they open in the reading panel next to the roadmap, same as the article links on each core concept.
- Functions — Python Functions & modules (Corey Schafer)
Explanation
Functions package repeatable steps; modules package repeatable files. Python’s import system is how small scripts grow into maintainable services.
Functions
def creates a callable object. Return sends a value back; omitting return gives None. Default arguments are evaluated once at definition time — never use mutable defaults like []; use None and create inside the function.
*args / **kwargs
*args collects extra positional arguments into a tuple; **kwargs collects keyword arguments into a dict. Decorators and wrappers use them to forward unknown arguments to inner functions — like a receptionist passing every visitor message to the right office.
Modules
Each .py file is a module. Imports run top-level code once, then cache in sys.modules. Prefer explicit imports (from x import y) over import * to keep namespaces readable.
Packages & pip
Packages group modules under a directory. pip installs third-party packages into your active venv. Lock files ensure CI and production install identical dependency graphs — reproducibility beats “works on my laptop.”
name == "main"
When Python runs a file directly, __name__ is "__main__". When imported, it is the module name. Guard script-only code (CLI parsing, server start) under this check so imports stay side-effect free.
What Is It?
Functions are defined with def; modules are files; packages are directories (with init.py or namespace packages).
Import paths mirror filesystem layout — understanding that fixes half of ImportError tickets.
Virtual environments change which site-packages directory satisfies an import.
Why Should I Care?
Agent SDKs and FastAPI apps split into dozens of modules — import hygiene keeps refactors safe.
Knowing package structure is required to publish libraries or monorepos.
Real World Analogy
A Python package is a folder with an __init__.py name tag on the door — it tells the interpreter 'this directory is one importable unit.'
Visual Mental Model
mypkg/__init__.py
mypkg/utils.py <-- import mypkg.utilsBeginner Explanation
Learn def, *args/**kwargs, and how imports resolve. Third-party code lands via pip into site-packages, then imports like stdlib.
Smallest Possible Example
def total(*nums: int) -> int:
return sum(nums)
print(total(1, 2, 3))Common Mistakes
❌ Circular imports between two modules.
Extract shared types or functions to a third module imported by both.
❌ Using from module import * in application code.
Pollutes namespace and hides origins. Import names explicitly.
❌ Running modules without -m when relative imports are used.
Use python -m pkg.module so package context is correct.
Real Project Example
Django apps are Python packages with models, views, and urls modules.
LangChain and Pydantic AI organize tools across subpackages — same import rules.
Build This Mini Project
Create mypkg/ with two modules, one relative import, and a CLI entry run via python -m mypkg.main.
Interview Question
What do *args and **kwargs mean?
*args collects extra positional arguments into a tuple; **kwargs collects extra keyword arguments into a dict. They let wrappers forward arguments to inner functions — common in decorators.
Key Takeaways
- Modules map to files; packages map to folders.
- Prefer explicit imports.
- Use python -m for package execution.
- *args/**kwargs enable decorators and wrappers.
Resources
The 60-second story
A diner has a window where you place an order and a kitchen that should not depend on who is shouting. def is the window. You pass the table number and the dish. You get food back, or you get nothing if the function forgets to return. People stare at None and think the kitchen is haunted. The kitchen just did not hand anything through the window.
Default arguments are “fries unless they say otherwise.” The famous bug is a shared plate. If the default is a list, def add(item, plate=[]), every customer writes on the same plate, because Python builds that plate once, not once per order. Use None and create a fresh plate inside the function. That bug has lived in production longer than some of the engineers who meet it.
*args is “they keep adding sides and I will not name them all.” **kwargs is “they also handed me a napkin with extra notes.” Wrappers and decorators live on those, because they have to forward an order they did not design.
A module is one recipe card, a .py file. A package is a folder of cards. import json means “bring the whole card.” from pathlib import Path means “I only need this one tool from the drawer.” pip is how cards from other diners get onto your shelf. Lock the versions or Tuesday’s card will not match Monday’s soup.
if __name__ == "__main__" is the difference between a card you cook from and a card someone else imports as a step. When you run the file, it is the main show. When another file imports it, it should not automatically start the grill.
Map the diner.
defis the order window.- A returned value is the plate handed back.
- A mutable default is one shared plate for every customer.
*argsand**kwargsare unnamed sides and extra notes.- A module is a recipe card.
- A package is a folder of cards.
__name__ == "__main__"means “only fire the grill if this card was the one they ordered.”
def add_topping(item, plate=None):
if plate is None:
plate = []
plate.append(item)
return plateFunctions were split into modules because one file named everything.py becomes a diary. Imports exist so the diary can be a library. The __main__ guard exists so importing a helper does not launch the whole diner.
Mental model: an order window, and a fresh plate per customer. Shared default lists are how you serve the previous table’s fries by accident.
You have the page if you can warn a teammate about mutable defaults in one sentence and explain why import should not start the app.
