A module is just a Python file. The moment you save a function in helpers.py, you can import it from any other file in the same folder.

A first module

Create two files in the same folder:

def greet(name: str) -> str:
    return f"Hello, {name}!"


def shout(message: str) -> str:
    return message.upper() + "!"
import greetings

print(greetings.greet("Manikandan"))   # Hello, Manikandan!
print(greetings.shout("python"))        # PYTHON!

Run main.py:

uv run python main.py

That’s it. No setup, no registration. import greetings finds the file greetings.py next to main.py and treats it as a module.

You can also import specific names:

from greetings import greet

print(greet("Manikandan"))

Module-level code

Anything outside a function or class runs when the module is imported. This is sometimes useful:

PI: float = 3.14159
TAU: float = 2 * PI
GRAVITY_M_S2: float = 9.81

print("constants module loaded")

When main.py runs import constants, the print runs once.

For configuration that should always be available — constants, lookup tables, registered functions — module-level code is the right tool. For anything else (especially side effects like opening files), keep it inside functions.

The if __name__ == "__main__": trick

A module can be both imported and run directly. To run code only in the “directly run” case, use this pattern:

def greet(name: str) -> str:
    return f"Hello, {name}!"


if __name__ == "__main__":
    print(greet("World"))

What this means:

  • When you run uv run python greetings.py, Python sets __name__ to "__main__". The if is true, and print(greet("World")) runs.
  • When another file imports greetings, Python sets __name__ to "greetings". The if is false, so the print doesn’t run.

This lets you ship a module that’s also a runnable script. Common in CLI tools and quick test scripts.

Naming modules

Module names follow the same rules as variables — snake_case, all lowercase:

parse_user.py      good
parseUser.py        bad
ParseUser.py        bad
helpers.py          good
HelpersAndStuff.py  bad

Short, descriptive, lowercase. Avoid hyphens (they’re not valid Python identifiers).

Don’t shadow standard library names

If you create random.py, your file will be imported instead of Python’s random module — and nothing else will work right. Same for json.py, os.py, math.py.

Check the name doesn’t clash before you save:

>>> import random
>>> random
<module 'random' from '...'>

If something already comes up, pick a different name for your file.

A small project shape

A typical small Python project:

my_project/
├── main.py
├── helpers.py
├── data.py
└── pyproject.toml
def parse_int(text: str) -> int | None:
    try:
        return int(text)
    except ValueError:
        return None
from pathlib import Path
import json


def load_records(path: Path) -> list[dict[str, str]]:
    with open(path, encoding="utf-8") as f:
        return json.load(f)
from helpers import parse_int
from data import load_records
from pathlib import Path


def main() -> None:
    records = load_records(Path("data.json"))
    for r in records:
        age = parse_int(r["age"])
        print(r["name"], age)


if __name__ == "__main__":
    main()

Each file has one focused job. main.py glues them together.

What’s next

A single folder works for a handful of files. Beyond that, you organise modules into packages — folders of related modules.

Toggle theme (T)