[Python-ideas] Replacing the if __name__ == "__main__" idiom (was Re: making a module callable)
Steven D'Aprano
steve at pearwood.info
Mon Nov 25 15:12:21 CET 2013
On Mon, Nov 25, 2013 at 08:20:23PM +1000, Nick Coghlan wrote:
> So, rather than the low level "is_main", perhaps a higher level
> builtin would be appropriate?
>
> >>> def run_if_main(f):
> ... import sys
> ... if sys._getframe(-1).f_globals.get("__name__") == "__main__":
> ... sys.exit(f(sys.argv))
> ...
>
> Starting to get a little too magical for my taste at that point,
> although with a small tweak (to return "f" in the "not main" case) it
> *does* allow the idiom:
>
> @run_if_main
> def main(argv):
> ...
I love decorators, I really do, but I strongly feel that a decorator is
completely the wrong API for this functionality. I can't quite put it
into words, it's an aesthetic thing, but there is one concrete objection
I can give.
To understand the current "if __name__" idiom, the user only needs to
understand the if statement and the rule that Python defines a special
global variable __name__. To understand this run_if_main idiom, the user
has to understand decorators. It's hard to believe, but I've come across
people -- even quite experienced Python developers -- who avoid
decorators because they don't quite get how they work. And of course
beginners have no idea what a decorator is. These means explaining about
higher-order functions.
For something as basic as running a main function when called as a
script, one shouldn't have to understand functional programming, higher
order functions and decorators.
Here are the alternatives, as I see them:
(1) Keep the status quo.
I don't actually dislike the status quo, even though
after 15 years I still write "if __name__ is '__main__'" and have to go
back and correct it :-)
This is a bit magical, so although it's a perfectly acceptable solution,
perhaps we can make something a bit less magical? (Or at least, stick
the magic in the implementation, rather than in the user's code.) This
has suited Python well for 20-odd years, and there's nothing really
wrong with it.
+0.5 on this.
(2) Have a special function, called "main" or more likely "__main__",
which is automagically called by Python if and only if the module is
being run as the main module.
I still have a soft-spot for this, but I'm now satisfied that it is the
wrong solution. Arguments against:
- Explicit is better than implicit.
- Because it needs support from the compiler, you can't back-port
this to older versions of Python.
- People will be confused by the fact that __main__ is sometimes
automatically called and sometimes not.
-1 on this one.
(3) Add an is_main() function to simplify the idiom to:
if is_main():
...
Pros:
- the magic of deciding whether we're running in the main module
is hidden behind an abstraction layer;
- even more easily understood than the current "if __name__" idiom;
- easily backported.
Cons:
- one more built-in.
I give this one a +1.
(4) Like #3 above, but make it a (read-only?) global variable, like
__debug__. Possibly spelled "__main__". The idiom becomes:
if is_main:
...
if __main__:
...
Pros:
- Some people might feel that "this is the main module" feels
more like a global variable than a function call.
Cons:
- If read-only, that requires some magic behind the scenes.
- If not read-only, then people will mess about with it and
get confused.
+0.5 if read-only, -0.5 if not.
(5) A decorator-based solution.
Pros:
- More explicit than automagically calling a function based
on its name.
Cons:
- Feels wrong to me. I realise that's entirely subjective.
- To me, "run the main function" seems far too basic an
operation to justify requiring the user learn about
decorators first.
- Have to import the decorator first.
- Unless it's a built-in, in which case, yet another built-in.
- If we go with this solution, the bike-shedding. Oh the bike-
shedding!
* what should it pass to the decorated function?
~ sys.argv
~ a copy of sys.argv
~ sys.argv[1:]
~ nothing at all
* should you be allowed to decorate more than one function?
* should the function(s) be called immediately, or queued up
and then run after the module is fully loaded?
* call sys.exit?
I'm -1 on this. There are too many slightly different flavours of this
solution, and none of them are really hard to solve once you know
whether or not your in the main module.
--
Steven
More information about the Python-ideas
mailing list