2

In an interactively running python session I kick off modules/programs I wrote using argparse (because these modules are meant to be used as commands from the shell prompt as well).

If I call the module with the wrong args parameters, argparse correctly spits out an error as expected, but unfortunately arparse then calls sys.exit() which in turn terminates the outer python session.

That's not what I want. How can I protect the outer interactive python session from being terminated without changing the sys.exit() code in the inner module and without wrapping my module in code. - I'm looking for something like a switch I can set or something I can do to the interactive python session before I start it, so that sys.exit() does not terminate it.

Update from the comments:

The resaon why I ask this question is emacs's python-mode (python.el): It effectively "pastes" the complete code of the module file in the python session. If I want to wrap this in a try..except I need to indent the whole module before I wrap it and hand over to the python-session buffer in emacs.

Here a sample module I use (most of my command line utilities use a similar template):

#!/usr/bin/env python
"""\
tool to do stuff
"""
__author__ = """halloleo"""
__version__ = """0.1"""

import logging
import sys
import os

import clitools # my own helpers

#
# Options
#    
def argParser(locArgs = None):
    parser = clitools.HelpLogArgParser(description=__doc__)
    parser.add_argument('files', metavar='FILE', nargs='+',
                        help='file to work on')
    parser.add_loglevel_group()
    return parser.parse_args(locArgs)

def doStuff(file)
    # do stuff
    print file

#
# main
#
if __name__ == '__main__':

    args = argParser()     
    clitools.infoLoggerConfig(args.loglevel)
    logging.debug("args = %s" % args)          

    for f in args.files
        dostuff(f)

# Local Variables:
# leo-python-args-to-send: "--debug c:/tmp/testfile"
# End:

BTW, I know now, this approach works. I have implemented it, but it is kind of scary to indent the whole module code... Here is what at the end I handover to the python session:

import sys; sys.argv = '''scraper.py --debug c:/tmp/testfile'''.split()
try:
    #!/usr/bin/env python
    """\
    tool to do stuff
    """

    .
    .
    .

    # Local Variables:
    # leo-python-args-to-send: "--debug c:/tmp/testfile"
    # End:
except SystemExit, e:
    print "Terminated  with exit code", e
4
  • 2
    Catch the SystemExit exception. Commented Nov 6, 2013 at 4:43
  • Basically all python-modes offer executes through a dedicated process, which seems the way to go. Need a clean example relying on open code to tell more. Commented Nov 7, 2013 at 10:35
  • Dedicated process is an ide, indeed, but then I have to make sure in the case the modyle does not exit via ys.exit() that the deciacted buffer gets cleaned up. - BTW, Example module I want to exexcute (without exiting) is provided above. Not "clean" enough? That's how the modules I have all look like. Commented Nov 7, 2013 at 14:36
  • @halloleo Meant an example which I may run and check. "Clitools" don't exist here. Commented Nov 7, 2013 at 16:32

3 Answers 3

4

Use a try/except block catching SystemExit. For example:

import sys

try:
    sys.exit()
except SystemExit:
    print "Tried to sys.exit()"
Sign up to request clarification or add additional context in comments.

6 Comments

Mmmmh, but the outer python session is already running (interactively) - and I don't want run the module always with typing all the try... except stuff around it! Might edit the question to make this clearer.
@halloleo: Could you show an example of the way you're calling argparse interactively that is leading to this error? That would help make it clear how to prevent it from throwing the exception.
Effectively I paste the complete code of the module file in the python session. Well that is, not I do this, but emacs' python mode (python.el does it for me. - So what I'm trying now, is to enhance the python.el's elisp function to wrap the python code with the try except; - kind of scary to indent the whole python code of file for execution in the python mode...
Update: the indenting amazingly works, but """ strings spanning multiple lines are obviously altered... :-( So it would be better if I could somehow leave the whole code unindented and than just call the main stuff of the module in a try except...
@halloleo: You can wrap the function you're calling in a decorator that takes care of the try/except and would make it easy. But if I saw an example of an actual line of code that broke it would make it easier
|
2

As documented in python's sys module, sys.exit does the following:

Exit from Python. This is implemented by raising the SystemExit exception, so cleanup actions specified by finally clauses of try statements are honored, and it is possible to intercept the exit attempt at an outer level.

So you can handle the exception in a try except block as any other exception.


argparse actually calls sys.exit from the exit method of argparse.ArgumentParser.

In some circumstances, it might be more worthwhile to subclass argparse.ArgumentParser to raise a more meaningful error:

import argparse
class BadCLIArgs(Exception):
    pass

class NonExitingArgumentParser(argparse.ArgumentParser):
    def exit(self, status=1, message=None):
        raise BadCLIArgs((status, message))

Or alternatively override error (as exit is also likely to be called for the --help flag.)

I've never really liked the approach of argparse to exit rather than raising a more meaningful exception.

2 Comments

The Exception approach doesn't give nice error messages when I use the module from a shell, but a stack trace. So for the shell case sys.exit is appropriate I think. - I just want to catch this in the interactive python session - with a general switch or so (see reworded question).
There is no general switch. You could add a switch to NonExitingArgumentParser so that exit would delegate to a argparse.ArgumentParser.exit if turned on (off). You could even have that switch toggle depending on whether you're in the interactive prompt or not.
0

I just came across this problem. It seems that the SystemExit error is raised when I was using

args = parser.parse_args(argv)

so my solution was simply to use a try-except with only this line:

def main(argv=None):
    ...
    try:
        args = parser.parse_args(argv)
    except SystemExit:
        return 0
    ...
    return 0

In my script code I use:

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

This still places a 0 in the interactive shell after displaying the error message, but it does successfully print the error message and stop the interactive shell from terminating.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.