PYTHON / MODULES AND PACKAGES
__name__ == '__main__' and script entry points
Tell whether a Python file was imported or run directly, and wire a clean main() entry point behind an if __name__ == '__main__' guard.
What you will learn
- Read __name__ to know if a file was imported or executed as the program
- Keep definitions at module level and only launch code inside the guard
- Write main(argv=None) that returns an exit code, wired via raise SystemExit(main())
- Know that python -m pkg runs pkg/__main__.py with __name__ set to '__main__'
Understanding __name__ == '__main__' and script entry points
Every module's global namespace contains a variable called __name__, and it is filled in by whatever loaded the code, not by the code itself. The import system sets it to the module's dotted name (utils, pkg.cli), while the interpreter sets it to the literal string '__main__' for the one file it was asked to execute, whether that came from `python app.py`, `python -m pkg.cli`, or code piped through stdin. So `if __name__ == '__main__':` is not special syntax at all; it is an ordinary string comparison against a variable the loader already set.
Top-level statements in a module run once per interpreter, the first time that module is imported, and that is exactly what makes unguarded scripts hostile to reuse. If a file parses sys.argv, opens a database connection, or starts a server at module level, then merely importing it performs that work: from a test file, from a REPL, from a documentation builder, or from a multiprocessing child process that re-imports the main module on spawn. The guard splits a file in two: names that are always safe to load, and launch code that fires only when this file is the program being run.
The conventional shape keeps the guarded block to a single line. Put the real work in `main(argv=None)` that returns an integer status, then write `raise SystemExit(main())` under the guard, so the program is also an ordinary callable function you can import and test. This matters because there are entry points that never touch the guard: `python -m pkg` executes pkg/__main__.py with __name__ set to '__main__', but an installed console script from pyproject.toml simply imports your module and calls `main` directly, so anything hidden under the guard is skipped.
CODE = '''
def area(r):
return 3.14159 * r * r
print("loading:", __name__, "->", "script" if __name__ == "__main__" else "imported")
if __name__ == "__main__":
print("entry point runs, area(2) =", area(2))
'''
print("--- exec with __name__ set to 'circle' (what import does) ---")
exec(CODE, {"__name__": "circle"})
print("--- exec with __name__ set to '__main__' (what python file.py does) ---")
exec(CODE, {"__name__": "__main__"})
print("--- this file itself ---")
print("__name__ is", repr(__name__))__name__ is just a string in the module's globals that the loader sets to the module name on import and to '__main__' for the file being executed, so the guard cleanly separates library code from launch code.
Worked examples
A demo block that importers never see
Definitions stay at module level while a self-test and a demo call live under the guard.
def slugify(text):
return "-".join(text.lower().split())
def _demo():
assert slugify("Hello World") == "hello-world"
assert slugify("Entry Points") == "entry-points"
print("self-test passed")
if __name__ == "__main__":
_demo()
print(slugify(" Script Entry Points "))Example explained
Line 1slugify is defined at module level, so `from slug import slugify` works no matter how the file is loaded.
Line 2_demo() only executes when this file is the program, so importing the module prints nothing and asserts nothing.
Line 3The asserts act as a zero-dependency smoke test you can trigger with `python slug.py` before reaching for a test runner.
main(argv) returning an exit code
Shows why main should take argv as a parameter and return a status instead of calling sys.exit itself.
def main(argv):
if len(argv) < 2:
print("usage: greet NAME")
return 2
print("hello,", argv[1])
return 0
if __name__ == "__main__":
print("no args ->", main(["greet"]))
print("with arg ->", main(["greet", "ada"]))
# real wiring in a file you ship: raise SystemExit(main(sys.argv))Example explained
Line 1main takes argv as an argument rather than reading sys.argv, so a test can hand it any command line it likes.
Line 2Returning 2 instead of calling sys.exit(2) means no SystemExit escapes into the caller when main is used as a plain function.
Line 3`raise SystemExit(main(sys.argv))` is the shipping form: SystemExit(0) exits quietly, SystemExit(2) sets a failing shell status.
argparse behind the guard
A CLI entry point whose parser can be exercised without touching the process's real arguments.
import argparse
def build_parser():
p = argparse.ArgumentParser(prog="shout")
p.add_argument("text")
p.add_argument("-n", "--times", type=int, default=1)
return p
def main(argv=None):
args = build_parser().parse_args(argv)
for _ in range(args.times):
print(args.text.upper())
return 0
if __name__ == "__main__":
main(["hi there", "--times", "2"])Example explained
Line 1parse_args(None) falls back to sys.argv[1:], so the argv=None default preserves normal command-line behaviour.
Line 2Passing an explicit list makes this demo deterministic and is exactly how you would test the parser from a test file.
Line 3Building the parser in its own function lets tooling inspect the CLI without running the program.
Unguarded top-level work fires on import
Loading a module executes its top-level statements, which is why launch code must sit behind the guard.
import sys, types
src = """
print("connecting to the database...")
rows = [3, 1, 2]
rows.sort()
"""
mod = types.ModuleType("store")
exec(src, mod.__dict__) # this is what import does to the file's code
sys.modules["store"] = mod
import store # already loaded: nothing re-runs
print(store.rows, store.__name__)Example explained
Line 1exec(src, mod.__dict__) mirrors the import machinery: the module body is just code run inside a fresh namespace.
Line 2The print happens during loading, not when you use the module, so any importer pays for that side effect.
Line 3The second `import store` finds the module in sys.modules and re-runs nothing, which is why side effects are hard to spot once cached.
Important notes
Running `python pkg/cli.py` and importing pkg.cli in the same process creates two distinct module objects, one under '__main__' and one under 'pkg.cli', duplicating module-level state and breaking isinstance checks; prefer `python -m pkg.cli`.
Anything under the guard is invisible to importers, including installed console scripts that call your function directly, so required setup belongs inside main(), not in the guard body.
Common mistakes
Typing `if __name__ == "main":` or `if __name__ == __main__:` — the first silently never matches so the script appears to do nothing, the second raises NameError.
Defining functions or constants inside the guard: `import mymodule` then succeeds but `mymodule.main` raises AttributeError, because those names only exist when the file is executed directly.
Leaving sys.argv parsing at module level because it works when run: importing the module in a test suite makes argparse read the test runner's flags and abort with SystemExit(2).
Try it yourself
Change, predict, then run
Write a file with a function `total(prices)` that sums a list, plus `main(argv=None)` that prints the total of a hardcoded list and returns 0; call it from an `if __name__ == "__main__":` block and confirm the printed line disappears if you change the guard's string to "main".
Open the Python workspaceCheck your understanding
A package declares the console script `tool = mypkg.cli:main` in pyproject.toml, and mypkg/cli.py configures logging inside its `if __name__ == "__main__":` block before calling main(). What does a user see when they run `tool`?
- The guard runs first, so logging is configured and then main() runs a second time.
- Nothing happens, because console scripts require a pkg/__main__.py file.
- Logging is never configured: the wrapper imports mypkg.cli, so __name__ is 'mypkg.cli' and the guard is False.
- The guard runs, because any command-line invocation sets __name__ to '__main__'.
Show answer
The generated console script is effectively `from mypkg.cli import main; raise SystemExit(main())` — a normal import, so __name__ is 'mypkg.cli' and the guarded block is skipped, leaving logging unconfigured. Option 3 is tempting because `python -m mypkg.cli` really does set __name__ to '__main__', but that is the runpy path, not the console-script wrapper; setup the program needs must live inside main().