Back to Blog
Python

Python **main**: The `__main__` Guard Explained

python **main**: Learn how the `__main__` guard and a `main()` function control Python entry points, and why they matter for scripts and importable modules.

__main__entry pointpython scriptsmodule importmain function
Illustration of a Python module entry point showing the `__main__` guard separating direct script execution from module import.

When the Python interpreter executes a file, it sets the module-level variable __name__ before running the code in that file. The value depends on how the file was started, and that single variable is the foundation of the python **main** idiom: the if __name__ == "__main__": guard that separates script execution from module import.

What __name__ Actually Contains

Every Python module has a built-in __name__ attribute. When a file is run directly, the interpreter sets __name__ to the string "__main__". When the same file is imported, __name__ is set to the module's name as it appears in the import statement.

Consider this file:

# module_a.py print(__name__)

Running it directly produces:

__main__

Importing it from another module produces:

module_a

That distinction is the entire basis for the entry-point guard. The value of __name__ is not a convention; it is assigned by the interpreter at startup and is stable for the lifetime of the process.

Why the if __name__ == "__main__": Guard Matters

Without a guard, every statement at module level runs both when the file is executed directly and when it is imported. That is rarely what you want. A script that opens a database connection, starts a server, or prints output will do all of that work the moment another module imports it.

The guard restricts that behavior:

def main(): print("Running as a script") if __name__ == "__main__": main()

When python app.py runs this file, the interpreter reaches the if and finds __name__ equal to "__main__", so main() executes. When another module does import app, __name__ is "app", the condition is false, and only the function definition is loaded.

This is the core of python **main**: the same file behaves differently depending on how it is entered, and the guard is what controls that behavior.

Defining a main() Function

The common pattern is to keep the executable logic inside a main() function and reserve the guard for calling it. Keeping logic in a function rather than at module level has a practical benefit: the function can be imported and tested without side effects.

A typical entry point handles command-line arguments explicitly:

import sys def main(argv=None): if argv is None: argv = sys.argv[1:] for arg in argv: print(arg) return 0 if __name__ == "__main__": raise SystemExit(main())

The argv=None default makes the function testable without touching sys.argv. The raise SystemExit(main()) pattern converts the return value into an exit code, so a non-zero return signals failure to the shell. If you do not need an exit code, a plain main() call is sufficient.

Running as a Script vs. Importing

The table below summarizes the two entry paths for the same file:

How the file is enteredValue of __name__Does the guard run?
python file.py"__main__"Yes
python -m file"__main__"Yes
import file"file"No

The python -m form matters for packages. Running python -m mypackage executes the package's __main__.py module, which is the standard way to give a package a command-line interface without relying on a single script file.

The guard behaves identically in both direct and -m execution because the interpreter marks the entry module as "__main__" in both cases. The difference is how the module is located on the import path, not how __name__ is assigned.

Common Mistakes with the Entry Point

Several errors appear frequently when developers first work with python **main**.

Putting all logic at module level is the most common one. A file that opens sockets or reads configuration at the top level will execute that work on import, which breaks tests and makes the module unsafe to reuse. Moving the work into main() and calling it from the guard fixes the problem.

Forgetting the guard entirely has the same effect. The file runs correctly when executed directly, but any import triggers the full script. This is especially visible in test suites that import modules for unit testing.

Calling exit() or sys.exit() inside main() instead of returning is a subtler issue. It works, but it makes main() harder to test because the call never returns normally. Returning an integer and letting the guard convert it to an exit code keeps the function composable.

Ignoring sys.argv is common in one-off scripts. If the script never needs arguments, that is fine. Once the script grows, threading sys.argv through main(argv) avoids global state and makes argument parsing testable.

Console Scripts and Package Entry Points

For code that ships as a package, the if __name__ == "__main__": guard is only half of the entry-point story. The other half is the console-script mechanism declared in pyproject.toml:

[project.scripts] my-tool = "mypackage.cli:main"

When the package is installed, the tooling generates a small executable that imports mypackage.cli and calls its main() function. The generated wrapper handles the __name__ == "__main__" check for you, so the module itself does not need the guard to work as a command-line tool.

The guard still matters for the module. It lets the same main() be called from the console script, from tests, and from a direct python -m mypackage run without duplicating logic. Keeping the guard in place means the module remains importable without side effects even when it is also exposed as an installed command.

Keeping the Entry Point Maintainable

The long-term value of the python **main** pattern is testability. A module with a thin main() that delegates to small, named functions can be imported in tests without executing anything. Each function can be exercised directly, and the entry point itself is reduced to a one-line call.

A practical structure looks like this:

def load_config(path): ... def process(items): ... def main(argv=None): config = load_config(argv[0]) process(config) return 0 if __name__ == "__main__": raise SystemExit(main())

The guard keeps the boundary between "this file is a library" and "this file is a program" explicit. As the project grows, that boundary is what prevents import-time side effects from leaking into tests and into other modules that depend on the package.

python **main**: Practical Usage and Code Examples | RYUSLOG DEV