[Import-SIG] Idea: Autorun functionality for Python modules (redux)
Nick Coghlan
ncoghlan at gmail.com
Mon May 22 22:02:34 EDT 2017
(Note: Posting to import-sig as this isn't something I'm actively
planning to pursue myself any time soon, but I want to ensure we don't
accidentally block this possibility while working on the proposal to
make it possible to run extension modules as Python scripts. )
PEP 299 is an old rejected PEP proposing a special "__main__()"
function for Python modules: https://www.python.org/dev/peps/pep-0299/
Three main points were cited in its rejection:
- the name clash with "import __main__"
- the lack of a clear strategy for supporting both newer versions of
Python that supported automatic execution of a suitably named function
as well as older versions that required an "if __name__ ==
'__main__':" block
- the status quo wasn't seen as particular broken and "it would be
more familiar to C/C++ programmers" wasn't a compelling argument for
adding a second way to do it
In an email discussion with Brandon Rhodes a few months back, he
lamented the apparent intransigence of the core developers on this
front, and I pointed out that nobody had ever actually made a
follow-up proposal that specifically addressed the rationale applied
in rejecting PEP 299, and put together a sketch of what such a
proposal might look like.
The first two technical points can be handled by:
1. Using `__run__` as the special function name
2. Setting "__main__.__autorun__ = True" prior to main module
execution, and allowing a module to delete it or set `__autorun__ =
False` to turn off the default autorun behaviour
With those two special attributes defined, the autorun protocol would be:
if getattr(main_module, "__autorun__", False):
try:
runmain = main_module.__run__
except AttributeError:
pass
else:
import sys
sys.exit(runmain(sys.argv)
Scripts that want to optionally invoke "__run__" explicitly for
compatibility with older Python versions can then check "__autorun__"
to see whether or not they need to start the application themselves:
if __name__ == "__main__" and not globals().get("__autorun__"):
import sys
sys.exit(__run__(sys.argv))
With the technical objections handled, we can then ask what concrete
benefits a `def __run__(argv):` model might offer over the existing
`if name == "__main__":` model:
def __run__(argv):
"""CLI documentation goes here"""
return 0
1. __run__ can go at the *top* of the script, rather than at the end,
giving a conventional "CLI function followed by supporting
definitions" structure
2. you gain access to sys.argv without having to import sys (testing &
REPL friendly!)
3. you can set the process return code without having to call sys.exit
(testing & REPL friendly!)
4. you can attach CLI docs to __run__, rather than forcing them into
the module level docstring
4. introspection tools can more readily discover modules that expose a
command line interface
5. command line scripts written this way are automatically easier to
test, since they're written in a functional style (argv goes in,
return code comes out)
6. the call-and-response functional structure is also likely to
provide a better long term base building block for interoperable CLI
frameworks
Cheers,
Nick.
--
Nick Coghlan | ncoghlan at gmail.com | Brisbane, Australia
More information about the Import-SIG
mailing list