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__". Theifis true, andprint(greet("World"))runs. - When another file imports
greetings, Python sets__name__to"greetings". Theifis 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.