Course progress Course outline 24 of 24 lessons available
Python Foundations
Data and Collections
Building Reliable Programs
Modeling with Objects
Professional Python
Advanced Python
Put code in labeled drawers
One long Python file is like one giant toy box: everything is there, but nothing is easy to find. Python lets us give each job a labeled drawer.
-
Module
one importable
.pyfile - Import package a folder of modules
- Distribution something pip can install
- Virtual environment one project's Python home
These labels are related, but not interchangeable. import loads a module or import package. python -m pip install ... installs a distribution. One distribution can provide several import packages, and its install name need not match its import name.
Import without spilling names
import statistics binds one module object. The dot shows where each tool came from:
import statistics
minutes = [20, 30, 40]
print(statistics.mean(minutes))
print(statistics.median(minutes))
Output:
30
30
This module prefix is a namespace. It lets your own name mean coexist with statistics.mean. A focused import is also possible:
from statistics import mean as average
print(average([10, 20, 30]))
Use aliases only when they clarify meaning. Avoid from statistics import *: a wildcard hides which names arrived and which old names they replaced.
The first successful import of a module in one Python process runs its top-level code from top to bottom. Later ordinary imports reuse the cached module. Top level should therefore mostly define functions, classes, and constants—not ask for input or start the program.
- First import run top-level definitions
- Remember cache the module object
- Main guard run app work only on purpose
def main():
print("Study report started.")
if __name__ == "__main__":
main()
When run as the entry file, __name__ is "__main__", so the message appears. When imported, __name__ is the module’s import name, so main() becomes available without running.
Give a package one front door
A regular package is a directory containing __init__.py and related modules:
project/
├── study_report/
│ ├── __init__.py
│ ├── calculations.py
│ └── __main__.py
└── tests.py
__init__.py marks this regular package and runs on the first package import. It may be empty, or it may re-export a small public interface. Inside the package, one leading dot means “from this package”:
from .calculations import summarize
__all__ = ["summarize"]
__all__ documents an intended interface; it is not a privacy or security wall. Avoid circular imports: if A imports B and B imports A, move shared work to a lower-level module or pass values across a clearer boundary.
-
Import
__init__.pyopens the API - Relative import the dot keeps package context
-
python -m
runs
__main__.py
Run a package from the directory that contains it:
python -m study_report
Do not run python study_report/__main__.py; that treats the inner file as a loose script and can break relative imports.
Build a runnable study-report package
Create the tree above. Put this in study_report/calculations.py:
def summarize(sessions):
totals = {}
for topic, minutes in sessions:
totals[topic] = totals.get(topic, 0) + minutes
return sorted(totals.items())
Put this in study_report/__init__.py:
from .calculations import summarize
__all__ = ["summarize"]
Put this in study_report/__main__.py:
from . import summarize
def main():
sessions = [("Python", 45), ("Git", 40), ("Python", 30)]
print("Study report")
for topic, minutes in summarize(sessions):
print(f"- {topic}: {minutes} minutes")
if __name__ == "__main__":
main()
Finally, put this in tests.py beside the package:
from study_report import summarize
assert summarize([]) == []
assert summarize([("Python", 20), ("Python", 30)]) == [("Python", 50)]
print("Tests passed.")
Run python tests.py, then python -m study_report. Expected output:
Tests passed.
Study report
- Git: 40 minutes
- Python: 75 minutes
The test import prints no report. The package exposes one stable operation, while __main__.py owns visible app behavior.
Check the source and give each project a home
A local file named statistics.py, json.py, or pathlib.py can shadow the real library. Inspect what Python loaded:
import statistics
print(statistics.__name__)
print(statistics.__file__)
The second line is machine-specific; verify that it points to the expected library, not your project. Fix the filename or layout instead of scattering edits to sys.path.
Create an isolated environment from the project root:
python -m venv .venv
source .venv/bin/activate
python -c "import sys; print(sys.prefix != sys.base_prefix)"
Windows PowerShell uses .venv\Scripts\Activate.ps1. The check should print True. Install a needed distribution with python -m pip install distribution-name, then leave with deactivate. Add .venv/ to .gitignore and record the creation commands; recreate the environment instead of moving or committing it. A venv isolates installed distributions, not your data files or import design.
Three tiny missions and the handoff
- Safe import. Move a temperature function to
conversions.py; keep display behind a main guard inweather_report.py. Confirm importing it prints nothing. - New package. Make a
reading_logpackage with one public summary function, an__init__.py, and a__main__.py. Run it withpython -m reading_log. - Environment proof. Create
.venv, activate it, verify the prefix check isTrue, deactivate, and confirm.venv/is ignored by Git.
Sharp corners: wildcard imports hide names, top-level work causes import side effects, direct execution can lose package context, and a local filename can load the wrong module.
- I can distinguish a module, import package, distribution, and venv.
- I can explain namespaces and first-import execution.
- I can use a main guard and a small
__init__.pyinterface. - I can run and test a package with
python -m. - I can inspect an imported module’s source.
- I can create, verify, leave, and recreate
.venv. - I completed the three tiny missions.
Next, you will use pathlib, with, CSV, and JSON to give this organized code safe local storage.