-
-
Notifications
You must be signed in to change notification settings - Fork 32.1k
bpo-39452: rewrite and expand __main__.rst #26883
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 16 commits
02e9edf
c95f69b
235e866
a292ab6
d7a1999
8398f08
1fcb2af
2bde063
d29fd2a
4c60f2c
2b5f710
7e495d7
56afeaa
2a398ef
1a9956c
c4b5cea
1f012b4
f095362
c42b706
7fe7f1c
c063da1
7c6b451
06bcb09
80756b3
647c471
457bbc9
3d9b3b9
dd68513
6ee7090
757b03a
8e86468
f33a081
14bad85
d150674
7b987cb
b9db705
168c774
c450171
077e7a4
eb42489
7c61e78
45a9425
073e9d7
ccb9004
f8630fa
1e86e02
9c87442
0d4fc8a
4e51333
6f0f82c
46e7668
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,25 +1,186 @@ | ||
|
||
:mod:`__main__` --- Top-level script environment | ||
================================================ | ||
:mod:`__main__` --- CLIs, import-time behavior, and ``__name__ == '__main__'`` | ||
============================================================================== | ||
|
||
.. module:: __main__ | ||
:synopsis: The environment where the top-level script is run. | ||
:synopsis: CLIs, import-time behavior, and ``__name__ == '__main__'`` | ||
jdevries3133 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
-------------- | ||
|
||
``'__main__'`` is the name of the scope in which top-level code executes. | ||
A module's __name__ is set equal to ``'__main__'`` when read from | ||
standard input, a script, or from an interactive prompt. | ||
In Python, ``__main__`` is not a single mechanism in the language, but in fact | ||
is part of two quite different constructs: | ||
|
||
1. The ``__name__ == '__main__'`` statement | ||
jdevries3133 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
2. The ``__main__.py`` file in Python packages | ||
|
||
Each of these mechanisms are related to Python :ref:`tut-modules`; both how | ||
users interact with them as well as how they interact with each other. See | ||
:ref:`tut-modules` for details. | ||
|
||
|
||
``__name__ == '__main__'`` | ||
--------------------------- | ||
|
||
``'__main__'`` is the name of the environment where top-level code is run. | ||
jdevries3133 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"Top-level code" means when a Python module is initialized from an interactive | ||
prompt, from standard input, from a file argument, from a :option:`-c` argument | ||
or from a :option:`-m` argument, but **not** when it is initialized from an | ||
import statement. In any of these situations, the module's ``__name__`` is set | ||
equal to ``'__main__'``. The only other context in which Python code is run is | ||
when it is imported through an import statement. In that case, ``__name__`` is | ||
set equal to the module's name; usually the name of the file without the | ||
``.py`` extension. | ||
|
||
As a result, a module can discover whether or not it is running in the | ||
top-level environment by checking its own ``__name__``, which allows a common | ||
idiom for conditionally executing code when the module is not initialized from | ||
an import statement:: | ||
|
||
if __name__ == '__main__': | ||
# Execute when the module is not initialized from an import statement. | ||
jdevries3133 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
... | ||
|
||
Idiomatic Usage | ||
^^^^^^^^^^^^^^^ | ||
|
||
Putting as few statements as possible in the block below ``if __name___ == | ||
'__main__'`` can improve the clarity of your code. Most often, a function named | ||
*main* encapsulates the program's primary behavior, creating this pattern:: | ||
|
||
# echo.py | ||
|
||
import sys | ||
|
||
def main(phrase: str): | ||
"Print the string to standard output" | ||
print(phrase) | ||
|
||
if __name__ == '__main__': | ||
main(' '.join(sys.argv)) | ||
jdevries3133 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
This has the added benefit of the *main* function itself being importable | ||
elsewhere:: | ||
|
||
# elsewhere.py | ||
|
||
import sys | ||
|
||
from echo import main as echo_main | ||
|
||
def echo_platform(): | ||
echo_main(sys.platform) | ||
|
||
The spirit of this design is inherited from the C programming language, where | ||
the function whose name is *main* is the entry-point of a program. In C, | ||
*main* also returns an integer, which becomes the exit code of the process. | ||
Zero typically indicates successful termination, and other codes indicate some | ||
type of failure. :func:`sys.exit` provides the API for exiting with an | ||
explicit exit code. A popular convention in Python is for *main* functions to | ||
jdevries3133 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
also return an integer which is then passed directly into :func:`sys.exit`, | ||
making it the exit code of the process:: | ||
|
||
# first_char.py | ||
|
||
import sys | ||
|
||
def main(argv: list[str]) -> int: | ||
try: | ||
print(f'The first character is: {argv[1][0]}') | ||
return 0 | ||
except IndexError: | ||
print('ERROR: first character could not be found. ' | ||
'Did you pass an argument?') | ||
return 1 | ||
|
||
if __name__ == '__main__': | ||
sys.exit(main(sys.argv)) | ||
|
||
|
||
``__main__.py`` in Python Packages | ||
---------------------------------- | ||
|
||
If you are not familiar with Python packages, see section :ref:`tut-packages`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps we can move discussion on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is a good idea. On the other hand, I like how this doc compares and contrasts There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One thing to think about as a far as moving this to the documentation about packages is that the documentation about packages uses an audio library as its example. I'm not sure whether shoehorning a CLI into an audio library would be natural? Just something to consider as we think about that route. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @yaseppochi is +1 on keeping this as it is. See #26883 (comment) point #4 |
||
Most commonly, the ``__main__.py`` file is used to provide a command line | ||
interface for a package. Consider the following hypothetical package, | ||
"bandclass": | ||
|
||
.. code-block:: text | ||
|
||
bandclass | ||
├── __init__.py | ||
├── __main__.py | ||
├── parent.py | ||
└── student.py | ||
|
||
``__main__.py`` will be executed when the package itself is invoked | ||
directly from the command line using the :option:`-m` flag. For example:: | ||
|
||
python3 -m bandclass | ||
|
||
This command will cause ``__main__.py`` to run. For more details about the | ||
:option:`-m` flag, see :mod:`runpy`. How you utilize this mechanism will depend | ||
on the nature of the package you are writing, but in this hypothetical case, it | ||
might make sense to allow the teacher to search for students or parents using | ||
:mod:`argparse`:: | ||
|
||
# bandclass/__main__.py | ||
jdevries3133 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
import argparse | ||
import sys | ||
|
||
from .parent import Parents | ||
from .student import Students | ||
|
||
parser = argparse.ArgumentParser() | ||
parser.add_argument('--student', | ||
help="lookup a student and print their information") | ||
parser.add_argument('--parent', | ||
help="lookup a parent and print their information") | ||
|
||
args = parser.parse_args() | ||
|
||
if args.student and student := Students.find(args.student): | ||
jdevries3133 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
print(student) | ||
sys.exit('Student found') | ||
jdevries3133 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
elif args.parent and parent := Parents.find(args.parent): | ||
print(parent) | ||
sys.exit('Parent found') | ||
else: | ||
print('Result not found') | ||
sys.exit(args.print_help()) | ||
|
||
|
||
|
||
Idiomatic Usage | ||
^^^^^^^^^^^^^^^ | ||
|
||
.. | ||
should the first paragraph of this section be removed entirely? I see that | ||
this suggestion conflicts with setuptools's docs, where they do use | ||
if __name__ == '__main__' in __main__.py files | ||
|
||
(https://setuptools.readthedocs.io/en/latest/userguide/entry_point.html) | ||
|
||
However, I still think that the suggestion makes sense at face value. This | ||
is my reasoning: | ||
|
||
It seems to me that it is almost always redundant, except in the case of | ||
console scripts where __name__ would be package.__main__. Even then, | ||
wouldn't you **not** want your code to be under a __name__ == | ||
'__main__' block in that case? If it were, the code you'd want to run | ||
wouldn't run when invoked as a console script. To me, this seems like | ||
another reason to tell users _not_ to guard code in __main__.py under | ||
an if __name__ == '__main__' block. __main__.py should always run | ||
from top-to-bottom; is that not the case? | ||
|
||
A module can discover whether or not it is running in the main scope by | ||
checking its own ``__name__``, which allows a common idiom for conditionally | ||
executing code in a module when it is run as a script or with ``python | ||
-m`` but not when it is imported:: | ||
|
||
if __name__ == "__main__": | ||
# execute only if run as a script | ||
main() | ||
Note that it may not be necessary to use the ``if __name__ == '__main__'`` | ||
statement in ``__main__.py`` itself. There is no reason for any other file to | ||
import something from ``__main__.py``. ``__main__.py`` will normally always be | ||
executed as the main program; therefore, ``__name__`` will always be | ||
``'__main__'``. There are exceptions to this norm, though. For example, if you | ||
have explicitly identified ``__main__`` as a console script entry point in | ||
:file:`setup.py`. See section :ref:`entry-points`. | ||
|
||
For a package, the same effect can be achieved by including a | ||
``__main__.py`` module, the contents of which will be executed when the | ||
module is run with ``-m``. | ||
For a very popular example of a package using ``__main__.py`` in our standard | ||
jdevries3133 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
library, see :mod:`venv`, and its' invocation via ``python3 -m | ||
jdevries3133 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
venv [directory]``. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
Rewrote ``Doc/library/__main__.rst``. Broadened scope of the document to | ||
explicitly discuss and differentiate between ``__main__.py`` in packages | ||
versus the ``__name__ == '__main__'`` statement. | ||
jdevries3133 marked this conversation as resolved.
Show resolved
Hide resolved
|
Uh oh!
There was an error while loading. Please reload this page.