Building Your Own Modules & Packages

Intermediate · 18 min read · ▶ live playground · ✦ checkpoint

A long Python file is not a badge of honor. When one file starts holding scoring rules, menu code, reports, and helper functions, you split it into modules, importable .py files, and packages, folders of related modules Python can import.

The goal is not more files. The goal is clearer jobs.

Split by responsibility

A module is one file with one clear responsibility. Think of modules as labeled drawers in the workshop from lesson 1.3: score tools in one drawer, app flow in another, report helpers in another. You open the drawer you need with import.

Here is a tiny project with two files:

score_app/
  app.py
  score_tools.py

score_tools.py owns the scoring rule. app.py owns the story of this small program.

PASSING_SCORE = 40
 
def score_line(player, score):
    if score >= PASSING_SCORE:
        return f"{player}: {score} (pass)"
    return f"{player}: {score} (keep practicing)"
 
def demo():
    print(score_line("Noor", 48))
 
if __name__ == "__main__":
    demo()
from score_tools import score_line
 
scores = {"Mina": 42, "Ravi": 35}
 
for player, score in scores.items():
    print(score_line(player, score))

Run app.py from the folder that contains both files:

$ python app.py
Mina: 42 (pass)
Ravi: 35 (keep practicing)

That import works because Python's module search path starts with the folder of the script you are running, as you saw in lesson 8.1. app.py can find the nearby score_tools.py file, create a module object from it, and bind the imported score_line name.

Keep the split boring:

  • Put reusable calculations in helper modules.
  • Put "run the app" flow in one main script.
  • Keep names honest: score_tools.py should not secretly manage dates.

Keep imports quiet with the main guard

The bottom of score_tools.py has a strange-looking line:

print("__name__ is", __name__)

__name__ is __main__

A dunder name is a special Python name with double underscores on both sides. __name__ is a dunder name that tells a file how Python is using it.

When you run a file directly, Python sets that file's __name__ to "__main__". When you import the same file as a module, Python sets __name__ to the module name, like "score_tools".

That makes this pattern possible:

if __name__ == "__main__":
    demo()

The code inside the if runs only when the file is run directly. It does not run when another file imports the module.

So python score_tools.py runs the demo:

$ python score_tools.py
Noor: 48 (pass)

But python app.py imports score_line without printing Noor's demo first.

Turn a folder into a package

A package is a folder of related modules that Python can import as one group. A regular package is the beginner-friendly kind with an __init__.py setup file inside it.

Imagine your snack tracker grows past one helper module:

snack_project/
  app.py
  snackbox/
    __init__.py
    labels.py
    totals.py

snackbox is the package. labels.py and totals.py are modules inside it. __init__.py runs when the package is imported, so it is a good place to expose the names the outside world should use.

from .labels import snack_line
from .totals import total_items

After that, outside code can write from snackbox import snack_line, total_items. That import reads nicely because the package decides what it offers. The app does not need to know which internal file holds each helper.

Do not turn every two-file script into a package. Use a package when a group of modules belongs together and you want one importable name for the group.

Choose absolute or relative imports

An absolute import starts from a top-level module or package name. In this project, snackbox is the package name, so from snackbox.labels import snack_line is absolute.

A relative import starts from the current package. A leading dot means "next to this file inside the same package," so from .labels import snack_line is relative.

Use this rule of thumb:

  • From app.py or other outside code, prefer absolute imports: from snackbox.labels import snack_line.
  • Inside the package, relative imports can keep sibling modules tied together: from .labels import snack_line.
  • Avoid clever import paths. If the import line needs a diagram, the package shape probably needs a rethink.

Relative imports only work inside packages. A plain script like app.py should not start an import with . because Python does not know a current package for that script.

Try it - build a package for this run

This playground uses TemporaryDirectory, a standard-library helper that creates a throwaway folder, and write_text(), a pathlib helper that writes text into a file. Full file writing comes in lesson 9.1; here they just create the tiny package needed for the import.

python — playgroundlive
⌘/Ctrl + Enter to run

The important part is not the temporary folder setup. The important part is the package shape: snackbox/__init__.py exposes names, internal modules hold focused tools, and the app imports the package instead of copying code.

Checkpoint

Answer all three to mark this lesson complete

Now your own code can grow without becoming one giant file. Next, you will step outside the standard library and learn how pip and virtual environments keep external code under control.

+50 XP on completion