Python packages & main, explained
A package is just a directory of modules Python treats as a namespace. The mechanics —
__init__.py, the __main__ idiom, and python -m — confuse a lot of people because they
touch how Python both imports and runs code. Here's how the pieces fit.
What makes a package
A package is a directory Python can import. Traditionally it contains an __init__.py file;
that file runs when the package is first imported and defines what the package exposes.
myapp/
__init__.py # marks/initialises the package
models.py
utils/
__init__.py
text.py
from myapp.utils.text import slugify # dotted path follows the directory tree
__init__.py can be empty, or it can re-export names so callers get a clean API
(from myapp.models import User instead of deep paths).
init.py and what to put in it
The __init__.py runs on import, so it's the place to set up the package's public surface.
A common pattern is to lift key names up and declare __all__.
# myapp/__init__.py
from .models import User
from .utils.text import slugify
__all__ = ["User", "slugify"] # what "from myapp import *" exposes
Keep it light — heavy work in __init__.py runs on every import of the package. Since Python
3.3, directories without __init__.py can act as namespace packages, but explicit
__init__.py remains the clear default for regular packages.
The name == "main" idiom
Every module has a __name__. When run directly it's "__main__"; when imported it's the
module's name. The guard lets a file be both an importable module and a runnable script.
# tool.py
def main():
print("running")
if __name__ == "__main__":
main() # runs only when executed directly, not on import
Without the guard, the script's code would execute as a side effect of importing it — which is also why multiprocessing requires this guard.
Running packages with -m
python -m package runs a module as a script while keeping proper package context, so
relative imports work. This is the right way to run code that lives inside a package.
python -m myapp.tool # runs myapp/tool.py with myapp as its package
python script.py # runs as top-level — relative imports would fail
-m adds the current directory to sys.path and sets up the package, which is exactly what
direct python path/to/file.py execution does not do.
main.py makes a package runnable
If a package contains a __main__.py, then python -m package runs that file. This lets a
whole package be executed like a command.
myapp/
__init__.py
__main__.py # python -m myapp runs this
# myapp/__main__.py
from .cli import run
run()
This is how tools like python -m http.server and python -m pip work.
Modern project layout
Real projects use a src layout with a pyproject.toml declaring the package. Keeping
code under src/ prevents accidentally importing from the working directory instead of the
installed package.
myproject/
pyproject.toml
src/
myapp/
__init__.py
...
tests/
After pip install -e ., import myapp works from anywhere, and tests run against the
installed package rather than loose files.
Recap
A package is a directory of modules, marked and initialised by __init__.py (run on
import — keep it light; use it to re-export a clean API). The __name__ == "__main__"
guard lets a file act as both an importable module and a runnable script. Run code inside
packages with python -m package so relative imports work, and add a __main__.py
to make a package itself executable. For real projects, adopt the src layout with
pyproject.toml and an editable install.