NOETRION

Choose a topic

DAY 18 OF 30 · TRANSFORM AND ORGANIZE

Modules and packages

Split responsibilities into importable files and keep reusable logic separate from script startup.

90–120 minutes suggested study timePython 3.12Not yet completed

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.

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

  1. 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.
  2. 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.
  3. 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.

DAILY CHECKPOINT

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.

01. What normally happens on a module's initial import?

02. What does the __main__ guard distinguish?

03. Why avoid naming a practice file json.py?

04. Must an installation name equal the import name?

05. Why run python -m pip for the intended interpreter?

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.