C.W.K.
Stream
Lesson 02 of 05 · published

Packages, __init__.py, and __main__.py

~18 min · package, __init__, __main__, namespace-package

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete

A package is a directory of modules

A directory becomes a Python package when it contains __init__.py (regular package) or follows the namespace package rules (no __init__.py required, 3.3+). Inside the package, files are modules; subdirectories are subpackages. The whole tree behaves as one importable namespace.

__init__.py — the package's setup

When you import mypkg, Python runs mypkg/__init__.py. Use it to: (a) re-export key names so callers can write from mypkg import Important instead of from mypkg.submodule import Important, (b) run package-wide setup, (c) define __version__. Keep it small — heavy work in __init__ slows every import.

__main__.py — making a package executable

Add __main__.py at the top of a package and you can run it with python -m mypkg. Useful for CLI tools shipped as packages. The same pattern is why python -m pytest and python -m http.server work.

__all__ — controlling wildcard imports

If a module defines __all__ = ["Foo", "bar"], then from module import * only imports those names. Without __all__, all public names (no leading underscore) are exported. Useful when you want to formalize what's in your public API.

Principle: Names with a leading underscore (_private) are by convention internal — not enforced by the language, but respected by tools and humans. Reserve them for things callers shouldn't depend on. Public names should be named to read well in from mypkg import name.

Code

A small package layout·python
# mypkg/
#   __init__.py
#   core.py
#   utils.py
#
# mypkg/__init__.py
# from .core import important_function   # re-export at package level
# __version__ = "1.0.0"
#
# mypkg/core.py
# def important_function():
#     return 42
#
# mypkg/utils.py
# def helper():
#     return "helped"

# Now callers can do:
# from mypkg import important_function   # works thanks to __init__.py re-export
# from mypkg.utils import helper          # always works, full path
# print(mypkg.__version__)
__main__.py — executable package·python
# mypkg/__main__.py
# import sys
# from .core import important_function
#
# def main():
#     args = sys.argv[1:]
#     print("running with", args)
#     print("result:", important_function())
#
# if __name__ == "__main__":
#     main()

# Run it from the parent directory:
# python -m mypkg arg1 arg2
#
# This is how things like 'python -m pytest', 'python -m http.server' work.
__all__ — exposing your public API·python
# mymodule.py
# def public_func():
#     return "hi"
#
# def _internal():
#     return "shouldn't be exported"
#
# class Public:
#     pass
#
# class _Private:
#     pass
#
# __all__ = ["public_func", "Public"]
#
# Now `from mymodule import *` only imports public_func and Public.
# Without __all__, it would import public_func and Public anyway
# (anything not starting with underscore), but explicit __all__ is clearer.
Namespace packages — no __init__.py needed·python
# Modern Python (3.3+) supports namespace packages — directories
# treated as packages without an __init__.py. Useful when a logical
# package is split across multiple physical directories (e.g.,
# plugin systems where each plugin contributes part of the namespace).
#
# /path1/myorg/plugin_a/...
# /path2/myorg/plugin_b/...
#
# Both contribute to a single 'myorg' namespace as long as neither
# has an __init__.py.
#
# For most application code, regular packages with __init__.py are fine
# and clearer about intent. Namespace packages are a library/framework feature.

External links

Exercise

Create a package /tmp/calc_pkg/ with __init__.py (re-exports add from core), core.py (defines add(a, b) returning their sum), and __main__.py (calls add with two command-line args and prints the result). Add /tmp to sys.path. Test by: (a) from calc_pkg import add and call it. (b) Run python -m calc_pkg 3 4 and confirm it prints 7.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.