Built-in is_main() function as a more beginner-friendly alternative to if __name__ == "__main__"

Yes, I fully agree that this is a very good point to stop spreading imperfect practices as well:

This very telling, self-explanatory decorator provides an alternative for people who don’t want to remove the if name == “main”: main() idiom altogether, be it for lack of understanding, concerns about future extensibility or any other reason.

2 Likes

This decorator requires the global scope to be ready to call the main function at definition time, instead of delaying the call to the end. I think it would be better if the main function could be set as a post-execute hook that gets called after the global scope is fully defined.

2 Likes

Fair point. The only way I see about this is an @atexit.register solution that does not run into problem that made @encukou cautious. Maybe this would be a reasonable way out? Maybe there is a more elegant way?

import atexit
import inspect
import sys
from collections.abc import Callable
from functools import partial
from typing import Any, ParamSpec, TypeVar

P = ParamSpec("P")
R = TypeVar("R")


def main_function(func: Callable[P, R]) -> Callable[P, R]:
    """A beginner-friendly alternative to if __name__ == '__main__': main() idiom."""

    def _atexit_clean_excepthook(exctype: Any, value: Any, traceback: Any):
        """Defers the execution of the main function until clean, no-error termination
        of the program."""
        atexit.unregister(main_function)
        sys.__excepthook__(exctype, value, traceback)

    if func.__module__ == "__main__":
        # If main() was not defined to take in sys.argv - run it as is.
        argspec = inspect.getfullargspec(func)
        if argspec.args or argspec.varargs:
            func = partial(func, sys.argv)
        sys.excepthook = _atexit_clean_excepthook
        atexit.register(func)
    return func

How is a decorator more beginner friendly? To actually understand the current idiom, someone needs to know:

  • what a variable is
  • how to write a conditional
  • what importing means
  • what running a script means

Actually understanding a decorator solution requires understanding all of the above plus second-order functions and closures, atexit and partial function application.

OK, so maybe the goal is not that new users understand the decorator method? But in that case, it’s just new boilerplate. Why is new boilerplate any better than old boilerplate, especially when the old boilerplate has decades of accumulated knowledge online?


If I had a time machine, I’d say that __name__ should just consistently always report the module name and that there be a __main__ boolean variable for flagging import vs script: if __main__: main().

10 Likes

Before you will make it more public.

Just to better understands costs of this update, consider to check how many repositories need to be updated. And this is only repositories with open code.

You probably can use some sort of automation to make pull requests to these repositories and convince them to use main as the name of the function.

2 Likes

Yeah, “there should be only one way of doing things” was once of my concerns. It’s not like these libraries would have to be updated, the idiom works perfectly fine, but having two ways of attaining the same result might indeed potentially cause some confusion instead of resolving it, at least until the new convention settles in after some time.

Of all the koans in the Zen of Python, that one is the most misunderstood.

Firstly, the emphasis is not on one way. That is only a secondary goal. The primary goal is at least one obvious way to do each task.

(Where obvious is, naturally, a matter of experience and knowledge. And also whether or not you are Dutch.)

But more than that, this koan is also an intentional joke.

I like to think that this was Tim Peters’ way of demonstrating the futility of trying to guarantee “only one way”, and the subjective nature of “obvious”.

2 Likes

Second-most in my opinion, after “Explicit is better than implicit” :slight_smile: Otherwise, yes, completely agree with you.

3 Likes

Here are a few thoughts from the perspective of someone who is self-teaching Python. For a bit more context: I was completely new to Python about a year ago. I had had previous experience with programming, many years ago.

I don’t understand why this one simple line of code causes some people so much angst. (I still remember an amazingly long thread-rant on this topic, on Twitter, from a few months ago.)

In the first place, its use is never required to make a basic script work. As many others have already observed, the first scripts one writes when beginning with Python are invariably standalone. So, if the goal is to illustrate some introductory concepts, a la The Python Tutorial, there’s no need to bring it up.

Second, it doesn’t take long, even when starting from scratch, to get to the point of learning about functions and why they are a Good Thing. (I did have the slight advantage here, from my earlier programming experience, of not needing to be persuaded of this concept.) Here again, though, it is still not necessary to use if __name__ == "__main__":; i.e., it is still perfectly fine to write standalone scripts that define and call functions, with the (implicitly) main part of the program still tight to the left edge of the screen.

I forget when I first saw if __name__ == "__main__": mentioned, but it was pretty early on in my learning journey. My reaction? It certainly seemed self-explanatory, just reading it as English (again, perhaps, from my familiarity with the idea of a main program/function from previous experience). The underscores? I just figured that they were a way of making reserved keywords stand out as such, and also a nicety that would help prevent new programmers from inadvertently using them.

My point here is that I had no idea, at the start, about magic methods. I wouldn’t come across that term for another little while, when I started learning about classes. I just saw this as something that appeared all over the place, that many people said was a good idea to use. So, sure, I made a couple of fragments of boilerplate code, stuck them in my /frags/ subdirectory, and just got in the habit of including one of them at the start of every new exercise.

In other words, while it’s fair to say I didn’t completely understand it, it certainly did not cause me any confusion or apprehension. It was merely one of those things that I figured I’d grasp more deeply in the future, if necessary, but was not preventing me from moving forward. As with any large topic, learning is not purely linear; there is always some circling back.

And then, of course, I got to the part of the learning journey where the concept of modules is introduced, and I started to understand that this wasn’t just an idiom or a piece of boilerplate, but was in fact really handy. Not only could I write, say, a standalone command-line utility, but I would then have the functions all ready to go, for inclusion in another script or program.

I also like if __name__ == "__main__": for when I’m testing out some library or third-party module, and for rapid development of a new function, when I’m sketching out an idea of my own that is, at the moment, only hazily formed. Someday, maybe, I’ll fully embrace TDD; but for now, when I’m just doing some exploratory/experimental work, it’s far more convenient just to have some calling code, tests, use demonstrations, debugging output, notes2self, etc., all in one place. (I work in the old-school way, bouncing back and forth between editor and terminal window.)

So, those are my two cents. In conclusion, I don’t think there is anything wrong with this one little line of code, I think there is a lot to like about it, and that it is not hard to understand, when beginning to learn, and I do not think Python itself needs to be modified in any way related to it.

6 Likes

To be entirely honest, I spent probably a decade or more as an
informally trained or untrained Python programmer (having some
amateur experience in C and other languages in my earlier days),
before ever putting a check for name == main in anything I
wrote. It wasn’t until I got involved with very large Python
projects already using that paradigm before I even realized what it
was even good for.

Previously, I just made a separate wrapper script which called the
primary routine from the core module in a program if I needed that
to both be runnable from the command line and importable by other
programs, and I still have some old projects I’ve never converted to
merging the script and module into one file. In later years, when I
discovered Python dists and entrypoint wrapper scripts, I saw I
could start just relying on those to replace my custom wrapper
scripts in actual software deployment anyway.

For me, the only reason to add a name == main check was so I
could directly run that module during development as a script
without needing to install the package first, but since most of my
development workflow these days involves tox (which sets up a venv,
installs the package into that, and calls entrypoint wrappers of my
choosing), the check is not all that useful even then.

2 Likes

There is one clear way to indicate this: use the __main__.py file as your entry point.

So if you have a package foo with the __main__.py file, the canonical way to invoke your script is:

$ python -m foo

Given the near universality of if… __main__ code, why not make the functionality a keyword? Something like:

run: main()

or

exec: main()

1 Like

It’s not near-universal, it definitely doesn’t need a keyword, and if you’re bothered by the number of times you’re copying and pasting if __name__=='__main__': around the place, I recommend reading up in this thread where it is clearly stated that you often don’t need to.

But even if it were near-universal among top-level scripts, that is still a very far cry froom needing syntax, much less a keyword.

1 Like

Should I interpret your response as one of the following?

  1. Even if it were near-universal among top-level scripts, it doesn’t mean that this idiom needs to be accounted for at all by Python. (no change)

  2. Even if it were near-universal among top-level scripts, it doesn’t mean that this idiom needs to be accounted for in syntax, much less a keyword, but still it would be good if Python accounted for this idiom in some different way than what we have right now. (open to some change)

Both, neither, but closer to this one. All I was saying was that this most certainly does not need to be a keyword, especially not a high-value keyboard like “run” or “exec”; and if it were something less precious as a keyword, it would be no better than the current idiom (imagine if it were run_if_script_is_toplevel main() - that wouldn’t break much, but it utterly sucks and isn’t much value over what we already have).

If you want to propose some non-syntactic change to the way this is done, go ahead, but I also don’t think this is common enough to need anything more than we currently have. I’m not saying that the current idiom is perfect (it isn’t, for quite a few reasons), but the cost of change outweighs the benefits of any of the proposals shown so far.

1 Like

I see what you mean now, thank you.

Consider, for example, @aroberge 's proposal of if not __imported__: main(). In a hypothetical scenario where the current idiom is near-universal among top-level scripts, would you say the cost of change (confusion for beginners, lots of learning material becoming out of date, etc.) still outweighs the benefits of this particular proposal?

If it WERE near-universal, that would be a problem, because it indicates that people are using it when it’s completely unnecessary. But let’s list off the upsides and downsides of this proposal:

  1. “Not imported” reads fairly well, slightly better than “if name is main”.
  2. It’s pretty easy to define the semantics. It’s a module-level boolean that is False in __main__, and True in all other modules.

However:

  1. It has to be introduced in some version of Python. In all previous versions, this idiom is going to NameError. So now you need a backward compatibility line: __imported__ = __name__ != '__main__' to ensure that your code works in all versions of Python.
  2. The existing idiom can’t be removed. So that backward compatibility line will continue to work forever. Which actually means that the feature itself is pretty much unnecessary, since the backward compatibility code is just one single line that doesn’t do anything special.
  3. You still have to treat this as boilerplate. There’s no way that anyone would just stumble upon it, which means it’s still going to get copied and pasted.

So this proposal has small issues, but no real benefits. Why bother?

The current idiom is one line of code. And you don’t need it at all unless your module is both imported AND run as a script. For any proposal to improve on this, it needs to be either zero lines of code, or be so utterly intuitive that people can figure it out without having to look it up - which is pretty much impossible.

1 Like

I see.

Let’s consider this. There are some very good Python coders, like Vitalik Buterin, who frequently write Python files which are imported as a module AND ran as a script. They don’t do that for no reason, as some people who are swayed by educational material. I imagine that a lot of these coders also would like to write nicer-looking code, otherwise they probably wouldn’t be writing a lot of code in Python. For now, regardless of whether they want to write backwards-compatible code or not, they have to use an idiom, which isn’t as nice-looking (or define a constant like IMPORTED, which from my observation very few people do). And for some of these good coders, very often backwards compatibility is not a huge concern.

Suppose that Python 3.14 introduced the if not __imported__ way of doing things. On that release, it changes very little for these good coders who strictly have to maintain backwards-compatible code, as they can just ignore an addition. It is a dead win for those good coders who don’t have much of a need to write backwards-compatible code, and they reap the benefit of a slightly better way of doing things. And has a small issue for people like big open-source package maintainers, who probably have to adapt to a new way of doing things in a backwards-compatible way by writing __imported__ = __name__ != '__main__' on top of the module.

So, on the day of release of Python 3.14, this addition would have some good coders who are happy with it, and some indifferent. Of course, there would be some initial resistance from those who maintain large open-source packages and would need to adapt to the new syntax. But, as Python versions get increased and older versions of Python get deprecated, the group of good coders who are happy with an addition increases as more people use Python, but a share of people who have a reason for annoyance gets smaller and smaller, as far away into the future fewer and fewer people will have to maintain code compatible with versions of Python before 3.14.

Think of this situation being analogous with all the rapid transition happening with type hint annotations. Some open-source maintainer have to use type hints exactly as they were defined in Python 3.5-3.8 era, but as more and more people find themselves not having to maintain Python 3.5-3.8 compatible code, they switch to better and more modern ways of using Python type hints. Changes that were made to Python start mattering to some people only years after.

Ultimately, I believe that while the backwards compatibility concern is valid in the short term. But as Python matures, the benefits of introducing a new syntax for importing modules and running them as scripts would outweight initial inconveniences.

What do you think?

I think this topic died over half a year ago, and resurrecting it to start having theoretical debates all over again isn’t a good use of anyone’s time. Closing.

2 Likes