Contact
Evil Genius SocietyHomeLearnForumLeaderboardSocialsMessages
← Back to module

Lesson 2 of 8

A module isn't special — any .py file is a module. Save functions in helpers.py and any other file can import helpers.

Creating one

Say greetings.py contains:

GREETING = "Hello"

def greet(name):
    return f"{GREETING}, {name}!"

def farewell(name):
    return f"Goodbye, {name}."

Another file in the same folder uses it:

import greetings

print(greetings.greet("Ada"))
print(greetings.GREETING)

The module name is the filename without .py. Functions, classes, and variables all come along.

Seeing it work here

The lessons run in a browser, so there's no folder of files to point at. But you already know how to create files — Module 6 — so we can write a module and import it in the same program:

import sys

with open("greetings.py", "w") as f:
    f.write('GREETING = "Hello"\n\n')
    f.write('def greet(name):\n')
    f.write('    return f"{GREETING}, {name}!"\n')

sys.path.insert(0, "")

import greetings

print(greetings.greet("Ada"))
print(greetings.GREETING)

sys.path.insert(0, "") puts the current directory first on the search path from lesson 7.1. You wouldn't normally write this — in a real project the files simply sit next to each other — but it makes the mechanism visible.

Why split files at all?

Once a script passes a few hundred lines, one file becomes hard to navigate. Splitting gives you:

  • Findability — validation logic lives in validation.py, not somewhere in a 900-line file.
  • Reuse — the same helpers.py serves several programs.
  • Testability — Module 9's tests import your functions directly.
  • Smaller diffs — two people can work on different files without conflicts.

Group by responsibility, not by type. storage.py, validation.py, reporting.py beat functions.py, classes.py, stuff.py.

if __name__ == "__main__"

Here's a problem. Code at the top level of a module runs on import:

import sys

with open("tools.py", "w") as f:
    f.write('def double(x):\n    return x * 2\n\n')
    f.write('print("tools.py is running!")\n')

sys.path.insert(0, "")
import tools
print(tools.double(5))

Importing tools printed its message — probably not what you wanted. Python sets a variable called __name__ in every module:

  • Run the file directly → __name__ is "__main__"
  • Import it → __name__ is the module name, e.g. "tools"

So guard anything that should only happen when the file is run directly:

def double(x):
    return x * 2

if __name__ == "__main__":
    print("Testing:", double(5))

Now importing tools gives you double silently, while running python tools.py executes the test code. You'll see this in almost every real Python file.

Modules are cached

Python imports a module once per program run, then reuses it:

import sys

with open("counter_mod.py", "w") as f:
    f.write('print("loading...")\nvalue = 42\n')

sys.path.insert(0, "")
import counter_mod
import counter_mod

print(counter_mod.value)

"loading..." appears once even though there are two imports. That's why imports are cheap to repeat, and why editing a module mid-session doesn't take effect until you restart.

Try it yourself

Write a module to a file, import it, and use it.

Notice "only when run directly" never printed — the __main__ guard did its job.

Checkpoint

  • Any .py file is a module; import filename (without the extension) loads it.
  • Split by responsibility once a file gets long.
  • Top-level code runs on import — guard it with if __name__ == "__main__":.
  • __name__ is "__main__" when run directly, otherwise the module's name.
  • Each module is imported and executed only once per run.

Questions about this lesson

Log in to ask a question.