7

In a project that has a __main__.py, rather than

# __main__.py
# def main...

if __name__ == "__main__":
    main()

...is it OK to just do:

# __main__.py
# def main...

main()

Edit:

@user2357112-supports-Monica's argument made a lot of sense to me, so I went back and tracked down the library that had been giving me issues, causing me to still be adding the if __... line. It's upon calling python -m pytest --doctest-modules.

Maybe that's the only place that makes a mistake in running __main__.py? And maybe that's a bug?

Reproduced by putting the first example in the docs in a __main__.py:

――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― package/__main__.py ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
package/__main__.py:58: in <module>
    args = parser.parse_args()
/usr/local/Cellar/python/3.7.7/Frameworks/Python.framework/Versions/3.7/lib/python3.7/argparse.py:1755: in parse_args
    args, argv = self.parse_known_args(args, namespace)
/usr/local/Cellar/python/3.7.7/Frameworks/Python.framework/Versions/3.7/lib/python3.7/argparse.py:1787: in parse_known_args
    namespace, args = self._parse_known_args(args, namespace)
/usr/local/Cellar/python/3.7.7/Frameworks/Python.framework/Versions/3.7/lib/python3.7/argparse.py:2022: in _parse_known_args
    ', '.join(required_actions))
/usr/local/Cellar/python/3.7.7/Frameworks/Python.framework/Versions/3.7/lib/python3.7/argparse.py:2508: in error
    self.exit(2, _('%(prog)s: error: %(message)s\n') % args)
/usr/local/Cellar/python/3.7.7/Frameworks/Python.framework/Versions/3.7/lib/python3.7/argparse.py:2495: in exit
    _sys.exit(status)
E   SystemExit: 2
--------------------------------------------------------------------------------------- Captured stderr ---------------------------------------------------------------------------------------
usage: pytest.py [-h] [--sum] N [N ...]
pytest.py: error: the following arguments are required: N

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 errors during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

Results (4.23s):
Maximilian
  • 7,512
  • 3
  • 50
  • 63
  • 2
    Before anyone says you're not supposed to actually call a file `__main__.py`: [there are in fact multiple cases where you really are supposed to call a file `__main__.py`](https://docs.python.org/3/using/cmdline.html#interface-options). – user2357112 Apr 24 '20 at 22:17
  • I can't think of any good reason to add the guards within `__main__.py`, since it's a file that is intended to be run as a script. `__name__` should always equal `"__main__"` here. – wim Apr 24 '20 at 22:21

1 Answers1

5

It's okay to skip the if __name__ == '__main__' guard in most regular scripts, not just __main__.py. The purpose of the guard is to make specific code not run if the file is imported as a module instead of run as the program's entry point, but importing a __main__.py as a module is usually using it wrong anyway.

Even with multiprocessing, you might think you need an if __name__ == '__main__' guard, but in the case of a __main__.py, it wouldn't actually help. It's commonly said that multiprocessing in spawn or forkserver mode imports the __main__ script as a module, but that's a simplification of the real behavior. In particular, one part of the real behavior is that if spawn mode detects the main script was a __main__.py, it just doesn't try to load the original __main__ at all:

# __main__.py files for packages, directories, zip archives, etc, run
# their "main only" code unconditionally, so we don't even try to
# populate anything in __main__, nor do we make any changes to
# __main__ attributes
current_main = sys.modules['__main__']
if mod_name == "__main__" or mod_name.endswith(".__main__"):
    return

forkserver mode also didn't load __main__.py when I tested it, but forkserver goes through a slightly different code path, and I'm not sure where it decided to skip __main__.py.

(This might be different on different Python versions - I only checked 3.8.2.)


That said, there's nothing wrong with using an if __name__ == '__main__' guard. Not using it has more weird edge cases than using it, and experienced readers will be more confused by its absence than its presence. Even in a __main__.py, I would probably still use the guard.

If you actually do want to import __main__.py for some reason, perhaps to unit test functions defined there, then you will need the guard. However, it might make more sense to move anything worth importing out of __main__.py and into another file.

user2357112
  • 260,549
  • 28
  • 431
  • 505
  • 1
    What about testing packages? Would they run the file on test collection? – Maximilian Apr 24 '20 at 23:29
  • 1
    @Maximilian: That might depend on how you're doing test discovery. [`unittest` test discovery](https://docs.python.org/3/library/unittest.html#unittest.TestLoader.discover) won't try to load a `__main__.py` unless you give it a weird test file pattern, for example. [pytest test discovery](https://docs.pytest.org/en/latest/goodpractices.html) won't try to load a `__main__.py` either. – user2357112 Apr 24 '20 at 23:32
  • I tracked down a reproduction that had been haunting me and added it above – Maximilian Apr 25 '20 at 07:34
  • 1
    @Maximilian: It looks like `--doctest-modules` is trying to import `__main__.py` files. I didn't see anything about it in the [pytest issue tracker](https://github.com/pytest-dev/pytest/issues?q=is%3Aissue+doctest-modules+__main__), so I doubt they've made an official decision one way or another whether they think this is correct behavior. It's probably worth bringing it up on the tracker. – user2357112 Apr 25 '20 at 07:54
  • FYI I added it as an issue to pytest: https://github.com/pytest-dev/pytest/issues/7124, and they responded favorably – Maximilian Feb 19 '21 at 18:22