What is best practice to define a main function and entry point for a script/module that may be used started as main, but not always?
Here's how I've been doing it in the past, similar to realpython:
def main():
result = do_stuff()
return result
if __name__ == "__main__":
main()
This is fine for most purposes and can give a return-code if the main function is revoked from another module, however I've recently encountered an interesting twist on it, returning exit codes:
def main():
result = do_stuff()
return result
if __name__ == "__main__":
import sys
sys.exit(main())
This has the benefit of the script being easily usable as part of any other script/program, given the entire routine is required and has a useful return value.
Then, if we add argparse to the mix it gets interesting. If we try to import our module, we'll have a problem in how we use the arguments. Sure we could start our script as it's own process, but that is needlessly inefficient.
So in a scenario with arguments and returncodes in the mix I do it like this:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('arg1', ...)
def main(args=parser.parse_args('')):
result = do_stuff(args.arg1)
return result
if __name__ == "__main__":
import sys
sys.exit(main(args=parser.parse_args()))
This way the module can be imported by other programs and if the main is to be used, you'll just have to pass the argument in a namespace that you create yourself with the wanted arguments. The default value for the main is a parse_args('')
call with an empty string, which initializes the namespace with all values as None
instead of getting sys.argv
by itself. To make that difference more verbose and as sys is imported for passing the exit-code anyway, once could pass the args manually and have the last line be like this:
sys.exit(main(args=parser.parse_args(sys.argv[1:])))
So the last one is the one I'd consider going with for future Python programs/scripts. Is this good practice? Is there a better way to do this?
sys.argv
." See also e.g. stackoverflow.com/a/18161115/3001761. I would also suggest the explicit arguments tomain
would be better than a single argument that's the whole namespace, as the former would support default values, type annotations, etc.exit
.main
actually does return an exit code in that way. I would consider that coupling to the CLI interface,main
should return or throw a business-related value, which the code underif __name__ == "__main__":
turns into an appropriate exit code. For reuse elsewhere the exit code may not make sense (unless main is that CLI wrapper and the business logic sits in some other function it invokes).