The Artima Developer Community
Sponsored Link

Weblogs Forum
Python main() functions

29 replies on 2 pages. Most recent reply: Mar 13, 2012 11:08 PM by skippy V

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 29 replies on 2 pages [ 1 2 | » ]
Guido van van Rossum

Posts: 359
Nickname: guido
Registered: Apr, 2003

Python main() functions (View in Weblogs)
Posted: May 15, 2003 8:56 PM
Reply to this message Reply
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()?


Michael McFarland

Posts: 1
Nickname: sidlon
Registered: May, 2003

Re: Python main() functions Posted: May 16, 2003 10:54 AM
Reply to this message Reply
For scripts that don't need a whole lot of commandline options, I've always liked the minimal approach:

------ Minimal Template ------

def usage():
print "Usage: %s" % os.path.basename(sys.argv[0])

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.

- Michael

Guido van van Rossum

Posts: 359
Nickname: guido
Registered: Apr, 2003

Re: Python main() functions Posted: May 18, 2003 4:20 PM
Reply to this message Reply
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/

Martin Blais

Posts: 2
Nickname: blais
Registered: Mar, 2003

Re: Python main() functions Posted: May 22, 2003 9:32 PM
Reply to this message Reply
I'm getting addicted to just do something like this now do deal with simple errors, e.g.::

...
except IOError, e:
raise SystemExit("Error: %s" % str(e))

(I don't think I need the str()).

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.

optparse rocks BTW...

Ned Batchelder

Posts: 1
Nickname: ned
Registered: May, 2003

Re: Python main() functions Posted: May 30, 2003 7:19 AM
Reply to this message Reply
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.

Guido van van Rossum

Posts: 359
Nickname: guido
Registered: Apr, 2003

Re: Python main() functions Posted: May 30, 2003 8:05 AM
Reply to this message Reply
> 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:
>

> 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.

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:

def main(argv=[__name__]): ...

if __name__ == "__main__": main(sys.argv)

Andy Gimblett

Posts: 2
Nickname: gimbo
Registered: Jun, 2003

Re: Python main() functions Posted: Jun 2, 2003 10:45 AM
Reply to this message Reply
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.
import getopt
import sys

def main(argv=None):

if argv=None:
argv=sys.argv
try:
config = get_config(argv[1:])
except UsageError, ex:
usage(ex)
sys.exit(2)

# program-specifc stuff from here on...

# blah blah blah...

def usage(ex):
if str(ex) == 'Help requested':
print __doc__
elif str(ex) == 'Version information requested':
version_info()
else:
sys.stderr.write("\n%s\n" % (__doc__))
sys.stderr.write("Error: %s\n" % (str(ex)))

def version_info():
version = "%s v%s" % (os.path.basename(sys.argv[0]), __version__)
print version

class UsageError(Exception):
pass

if __name__ == '__main__':
main(sys.argv)

Admittedly I haven't tended to use this from the interactive prompt much, so I'm not sure what the implications would be...

Guido van van Rossum

Posts: 359
Nickname: guido
Registered: Apr, 2003

Re: Python main() functions Posted: Jun 2, 2003 10:52 AM
Reply to this message Reply
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.

Andy Gimblett

Posts: 2
Nickname: gimbo
Registered: Jun, 2003

Re: Python main() functions Posted: Jun 3, 2003 5:42 AM
Reply to this message Reply
*nods* - yes, and I think I'll revise my method accordingly. I just wanted to point out the way I was using UsageError, really.

Dean Roberts

Posts: 1
Nickname: deano
Registered: Jun, 2003

Re: Python main() functions Posted: Jun 3, 2003 3:13 PM
Reply to this message Reply
Why not:

def main(argv=sys.argv):
etc.
etc.

It makes the code cleaner, and achieves the same effect, I believe.

Guido van van Rossum

Posts: 359
Nickname: guido
Registered: Apr, 2003

Re: Python main() functions Posted: Jun 3, 2003 3:20 PM
Reply to this message Reply
> Why not:
>
> def main(argv=sys.argv):

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.

Anthony Tarlano

Posts: 9
Nickname: tarlano
Registered: Jun, 2003

Re: Python main() functions Posted: Jun 22, 2003 7:32 AM
Reply to this message Reply
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__)

try:
opts, args = getopt.getopt(argv[1:], "h", ["help"])
except getopt.error, msg:
raise Usage(msg)

# option processing
for option, value in opts:
if option in ("-h", "--help"):
raise Usage(__doc__)

mkscriptfile(args[0])

except Usage, err:
print >>sys.stderr, sys.argv[0].split('/')[-1] + ": invalid " \
+ str(err.msg)
print >>sys.stderr, "\t for help use --help"
return 2

def mkscriptfile(scriptname):
os.system('touch '+ scriptname)
os.system('chmod +x ' + scriptname)

lines = """#!/usr/bin/env python

# 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

def main(argv=None):
if argv is None:
argv = sys.argv
try:
try:
opts, args = getopt.getopt(argv[1:], "hov:", ["help", "output="])
except getopt.error, msg:
raise Usage(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

if __name__ == "__main__":
sys.exit(main())"""
os.system("echo '" + lines + " ' >> " + scriptname)

if __name__ == "__main__":
sys.exit(main())


Anthony Tarlano

Posts: 9
Nickname: tarlano
Registered: Jun, 2003

Re: Python main() functions Posted: Jun 22, 2003 8:27 AM
Reply to this message Reply
I discovered a slight error in my script. You should replace the line:


print >>sys.stderr, sys.argv[0].split('/')[-1] + ": invalid " \
+ str(err.msg)


with


print >>sys.stderr, sys.argv[0].split('/')[-1] + ": " \
+ str(err.msg)


- Anthony

Matt Gerrans

Posts: 1153
Nickname: matt
Registered: Feb, 2002

Re: Python main() functions Posted: Jun 22, 2003 12:06 PM
Reply to this message Reply
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:
   output = file(filename,'w')
   output.write(lines)
   output.close()

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).

Anthony Tarlano

Posts: 9
Nickname: tarlano
Registered: Jun, 2003

Re: Python main() functions Posted: Jun 22, 2003 3:24 PM
Reply to this message Reply
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 29 replies on 2 pages [ 1  2 | » ]
Topic: Down on the Upside Previous Topic   Next Topic Topic: A Test of Knowledge


Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2014 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use - Advertise with Us