Today's outcomes
- Explain modules, packages, and import lookup.
- Use the __main__ guard.
- Distinguish a distribution name from an import name.
Before you start: Complete Day 17 and its practical challenge.
Understand the concept
A module is a reusable unit of source
A Python module is commonly a file containing definitions and statements. Another file can import it and use its functions. Splitting source by responsibility makes the dependency visible: a report module can call a parsing function without owning the parser's implementation.
Import does more than copy names into the caller. Python locates and loads the module, executes its top-level statements during initial loading, and caches the loaded module in the process. A top-level prompt or print in a helper can therefore run unexpectedly when another script imports it.
Choose informative file names that do not shadow a standard-library module. A practice file called json.py can interfere with importing the actual json library. The same is true of names such as random.py or datetime.py. If an import points at an unexpected file, inspect the module's file location and your current working directory.
The main guard separates startup from reusable definitions
When a file is run as the entry script, its __name__ is "__main__". When imported normally, its name identifies the module. A guard if __name__ == '__main__': lets you run a demonstration only for direct execution while keeping definitions available to importers.
The guard does not stop every top-level statement from executing. Definitions and other statements outside it still run during import. Place interactive startup inside a main function and call that function under the guard. Keep ordinary helper modules quiet unless their documented purpose requires otherwise.
Packages and distributions are related but different
A package organizes modules under a shared import namespace. A simple regular package commonly uses a directory with __init__.py; namespace packages are a separate supported mechanism and do not always require that file. Today's two-file exercise does not need a package directory yet.
A distribution is what a package installer installs. Its installation name can differ from the import name. For example, Day 26 installs beautifulsoup4 but imports bs4. Use the intended interpreter's python -m pip when managing packages so the installation targets the same interpreter you run. Keep a project's dependencies explicit rather than guessing which system environment contains them.
Worked example
Create the following two files in the same practice directory. Each code block names its destination. Run python main.py from that directory. This is intentionally a two-file example; the second block depends on the first.
# file: study_tools.py
def parse_minutes(text):
value = int(text)
if not 0 <= value <= 240:
raise ValueError("minutes must be between 0 and 240")
return value
def total_minutes(values):
return sum(values)
if __name__ == "__main__":
print("Helper demonstration:", parse_minutes("45"))
# file: main.py
import study_tools
def main():
values = [study_tools.parse_minutes(text) for text in ["30", "60", "0"]]
total = study_tools.total_minutes(values)
assert total == 90
print("Report total:", total)
if __name__ == "__main__":
main()
| Execution | Expected visible output | Why |
|---|---|---|
| python study_tools.py | Helper demonstration: 45 | Helper's direct-run guard is true |
| python main.py | Report total: 90 | Import does not run the helper demonstration |
| Import study_tools from another script | No demonstration line | Definitions remain reusable without startup output |
The module-qualified calls show where each function comes from. from study_tools import parse_minutes would bind that function directly in the caller, which can be readable too. Avoid wildcard imports that obscure the origin of names.
Guided practice
- Predict: move the helper demonstration outside its guard, then reason about the output from running main.py. Expect the import-time line before the report. Restore the quiet-import behavior afterward.
- Modify: add a display formatter to a third module and import it from main. Keep validation in study_tools and presentation in the formatter; do not create a circular dependency between helpers.
- Reconstruct: split yesterday's parser and audit into a helper file, with an entry script that supplies a fixture and displays the result. Run the entry script from a clean process rather than relying on notebook state.
An import path is resolved by the interpreter, not by what folder you happen to be viewing in an editor. Keep your first multi-file project simple: one practice directory, clear file names, and one documented entry command.
Daily challenge
Build challenge_tools.py containing count_at_least from Day 7, and challenge_main.py containing a main function that verifies and displays its result. Importing challenge_tools must produce no output. Running challenge_main.py must print Qualifying sessions: 2 for [59, 60, None, 80] at threshold 60.
| Requirement | Evidence |
|---|---|
| Reusable helper | Function accepts values and threshold explicitly |
| Quiet import | No prompts or demonstration prints during import |
| Explicit entry | Main guard calls the reporting function |
| Known result | Count equals two, including the exact boundary |
| Empty case | Helper returns zero without printing |
Do not install a third-party package for this task. Both files use Python itself. Include the names and entry command in a short README so another person can reproduce the run.
Reference solution
Compare after creating and running both files
# file: challenge_tools.py
def count_at_least(values, threshold=60):
return sum(1 for value in values if value is not None and value >= threshold)
# file: challenge_main.py
from challenge_tools import count_at_least
def main():
assert count_at_least([59, 60, None, 80]) == 2
assert count_at_least([]) == 0
print(f"Qualifying sessions: {count_at_least([59, 60, None, 80])}")
if __name__ == "__main__":
main()
The generator supplies one for each qualifying value, so sum counts matches. The helper's accepted-input assumptions remain those from Day 7. Module organization does not add validation automatically.
Debug and explain
If ModuleNotFoundError appears, inspect the intended interpreter, module filename, and invocation directory. If a standard-library import behaves strangely, look for a shadowing local filename. If duplicate demonstration lines appear, inspect import-time statements.
Explain the difference between importing and running a file, between a module and a distribution, and between a helper's contract and an entry script's responsibilities. This checkpoint completes the transition from a single learning script to a small organized program.
Review and next step
Keep the four source files and both expected runs. Rebuild the main guard without copying at the next review. Day 19 introduces objects when state and behavior belong together, while retaining the explicit interfaces and tests you already use.
Sources and verification
Original teaching examples checked with Python 3.12.14. Library behavior reviewed against these primary references on October 3, 2026. Day 26's examples also use Beautiful Soup 4.14.3.
Check your understanding
Five questions, 20 points each. Pass at 85% or above, which requires all five correct. Feedback explains each answer; revise the lesson and retry when needed.
Review your practical work
Check these statements after running your own challenge. This is your self-review; the website does not execute or assess your Python code.
Pass the knowledge check and finish the practical self-review to save completion.