Summary
For Python programmers, I've got some suggestions on how to write a main() function that's easy to invoke in other contexts, e.g. from the interactive Python prompt when you feel like experimenting.
Advertisement
I've written a few main() functions in my time. They usually have a
structure roughly like this:
"""Module docstring.
This serves as a long usage message.
"""
import sys
import getopt
def main():
# parse command line options
try:
opts, args = getopt.getopt(sys.argv[1:], "h", ["help"])
except getopt.error, msg:
print msg
print "for help use --help"
sys.exit(2)
# process options
for o, a in opts:
if o in ("-h", "--help"):
print __doc__
sys.exit(0)
# process arguments
for arg in args:
process(arg) # process() is defined elsewhere
if __name__ == "__main__":
main()
I'm sure many people write similar main() functions. I've got a few
suggestions that make main() a little more flexible, especially as
option parsing becomes more complex.
First, we change main() to take an optional 'argv' argument, which
allows us to call it from the interactive Python prompt:
def main(argv=None):
if argv is None:
argv = sys.argv
# etc., replacing sys.argv with argv in the getopt() call.
Note that we fill in the default for argv dynamically. This is more
flexible than writing
def main(argv=sys.argv):
# etc.
because sys.argv might have been changed by the time the call is made;
the default argument is calculated at the time the main() function is
defined, for all times.
Now the sys.exit() calls are annoying: when main()
calls sys.exit(), your interactive Python interpreter
will exit! The remedy is to let main()'s return value specify the exit
status. Thus, the code at the very end becomes
if __name__ == "__main__":
sys.exit(main())
and the calls to sys.exit(n) inside main() all become
return n.
Another refinement is to define a Usage() exception, which we catch
in an except clause at the end of main():
import sys
import getopt
class Usage(Exception):
def __init__(self, msg):
self.msg = msg
def main(argv=None):
if argv is None:
argv = sys.argv
try:
try:
opts, args = getopt.getopt(argv[1:], "h", ["help"])
except getopt.error, msg:
raise Usage(msg)
# more code, unchanged
except Usage, err:
print >>sys.stderr, err.msg
print >>sys.stderr, "for help use --help"
return 2
if __name__ == "__main__":
sys.exit(main())
This gives the main() function a single exit point, which is
preferable over multiple return 2 statements. This also
makes it easier to refactor the argument parsing: raise
Usage works just fine from inside a helper function, but
return 2 would require careful passing on of the return
values.
You might think that taking this to the extreme would move the
try/except clause out of the main() function, into the code at the end
of the module (if __name__ == "__main__": .... But that
would mean that when you call main() interactively, you'd get a
traceback for command line syntax errors, which isn't very helpful.
However, another generalization can be helpful: define
another exception, perhaps called Error, which is treated
just like Usage but returns 1. This can then be used for
expected errors like failure to open necessary files, which are not
command line syntax errors, but yet expected, and where again a
traceback doesn't feel very friendly.
What's your favorite convention for writing main()?
def main(): args = sys.argv[1:] if "-h" in args or "--help" in args: usage() sys.exit(2) # ...
------
When a script gets to the stage of needing getopt, I tend to do it like this:
------ Getopt Template ------
# set default options prefs = { 'verbose' : 0} def getPrefs(): """ Parse the commandline options. Store options in the global 'prefs' dict, and return the remaining arguments. """ try: opts, args = getopt.getopt(sys.argv[1:],"hv") except: print "Invalid options\n" usage() sys.exit(2)
for o, a in opts: if o == '-h': prefs['help'] = 1 elif o == '-v': prefs['verbose'] += 1 return args
def log(msg, priority=1): """ print the given message iff the verbose level is high enough """ if prefs['verbose'] >= priority: print >> sys.stderr, msg
def main(): args = getPrefs() log("PREFS: %s" % prefs) if "help" in prefs: usage() sys.exit(2)
------
Both of those templates can definitely benefit from your "main(argv=None)" idea.
As soon as you start building infrastructure around getopt, I suggest it's time to use Optik (incorporated in Python 2.3 as optparse). See http://optik.sourceforge.net/
I sometimes wish I could specify the exit code AND the message, but in general this is simple and I don't have to declare any user exception or anything.
I like most of the changes you suggest, but this part seems silly to me:
def main(argv=None): if argv is None: argv = sys.argv ...
if __name__ == "__main__": sys.exit(main())
Not because making main() callable is silly, but the style of the defaulting seems twisty and not useful. After all, who will let argv default other than the __main__ clause?
I'd rather have all the sys. stuff in one place:
def main(argv): ...
if __name__ == "__main__": sys.exit(main(sys.argv[1:]))
This is simpler (no need to explicitly test argv in main), makes the callable main more straightforward (no need to provide it with a dummy argv[0] which will be skipped), and keeps the connection with sys.argv more explicit.
> I like most of the changes you suggest, but this part > seems silly to me: >
> def main(argv=None): > if argv is None: > argv = sys.argv > ... > > if __name__ == "__main__": > sys.exit(main()) >
> Not because making main() callable is silly, but the style > of the defaulting seems twisty and not useful. After all, > who will let argv default other than the __main__ clause?
Actually, lots of times when calling main() from the interactive prompt, you don't want to pass arguments because you're happy with the default.
> I'd rather have all the sys. stuff in one place: >
> This is simpler (no need to explicitly test argv in main), > makes the callable main more straightforward (no need to > provide it with a dummy argv[0] which will be skipped), > and keeps the connection with sys.argv more explicit.
But some programs want to work argv[0] into the usage message; in that case you'd have to pass all of sys.argv.
As a compromise, I could live with something like this:
I usually use a structure along the following lines, which uses the UsageError's payload to differentiate between help requests, version number requests, and actual errors...
get_config() (not shown) either returns a populated dictionary of configuration options (parsed using getopt) or raises a UsageError with the appropriate payload.
But your approach always exits with status 2, even when help or the version is requested. Also, calling sys.exit() in main() has the drawback that started my exploration of an alternative.
I explained that in the initial post: it's possible that sys.argv is changed after the module is imported but before main() is called. What you write would freeze the original value of sys.argv as the default argument value.
Being inspired by this weblog, but also believing that templating is the best way for reuse of script recipes (and limiting my awful typing), I have adapted the code to be create by a template script.
The code itself is self documenting. I recommend for another user who finds it helpful to paste it to an executable file named newpy that is in your path...
Hope you all find it useful..
- Anthony
#!/usr/bin/env python
""" newpy(scriptname) -> scriptfile
Returns a new script file containing a main funtion template supporting command options using getopt that's easy to invoke in other contexts. The code in this script was adapted by Anthony Tarlano from code originally authored by Guido van Rossum found in his Weblog entry at http://www.artima.com/weblogs/viewpost.jsp?thread=4829 """
import os import sys import getopt
class Usage(Exception): def __init__(self, msg): self.msg = msg
def main(argv=None): if argv is None: argv = sys.argv try: if len(argv) != 2: raise Usage(__doc__)
# Replace this comment block as a module docstring, the module docstring # is used as default usage message i.e. raise Usage(__doc__) # example: # # file([file_type]) -> return_values # # Returns a ....
import sys import getopt
class Usage(Exception): def __init__(self, msg): self.msg = msg
# option processing for option, value in opts: if option == "-v": verbose = True if option in ("-h", "--help"): raise Usage(__doc__) if option in ("-o", "--output"): output = value
except Usage, err: print >>sys.stderr, sys.argv[0].split("/")[-1] + ": " + str(err.msg) print >>sys.stderr, "\t for help use --help" return 2
I used to use such a script I wrote (with that exact same name!), but I recently got around to figuring out how to get my editors to start a new file, based on a template and that is even more convenient. Actually, for one editor I often use, EmEditor, it is nearly a no-op: just put template.py in EmEditor's home directory.
By the way, why not just create file by writing instead of using system calls? Like so:
Or even file(filename,'w').write(lines) if you prefer one-liners.
Another thing I had in my "newpy.py" was that it would automatically open the new file in my editor of choice. Yet another feature you can add is to not have to specify a filename for the new script (then it can default to UntititledN.py (where N engenders the first available non-existent filename), or the like).
I totally agree, there are many ways to put the text in the file.
I guess I went with the os.systems calls because I am used to using the "touch", "chmod" and "echo" pattern from working with file centric operating systems like Plan9.
Please feel free to modify the script to suit your purposes..
Flat View: This topic has 31 replies
on 3 pages
[
123
|
»
]