Translating a Perl script into Python

Quinn Dunkan quinn at quetzal.ugcs.caltech.edu
Sun Jan 21 03:10:43 EST 2001


On Sat, 20 Jan 2001 22:40:07 GMT, Sheila King <sheila at spamcop.net> wrote:
>And now for a question about exceptions:
>I was searching the archives of the Tutor mailing list at Python.org and found
>a message that addressed exactly this topic.
>
>My understanding, now, of this situation (someone please confirm this):
>Perl's "die" statement basically causes the program to exit.
>Python, on trying to open the file, will throw an exception if the file is not
>able to be opened, and the program will automatically halt (or "die") if I do
>nothing to handle the exception. Also, it will print an appropriate error
>message to stderr without my doing anything special to cause that.
>
>If I wanted the program to not "die", I would have to handle the exception
>somehow. The point is, this script is so short, and relies on the file being
>opened when the script is invoked, if it cannot do so, the only appropriate
>course of action at that point is to halt execution of the script.

I agree entirely.  Another way to think of it is that the default perl thing
to do is to continue silently unless you explicitly put in error handling code
(of course, it usually can't continue for long before it hits something it
really can't ignore), while the python default is exactly the opposite.

The other thing to realize about exceptions is that they propagate.  If an
exception is thrown by a function that is not surrounded by try/except, it
will jump out of the current function, and see if *that* function is
surrounded by try/except.  If not, it will jump up again, until it either
encounters a try/except or gets to the top level of the script.  If it makes
it to the top, it prints the traceback and the script exits.  Another way to
think about it is that your whole script is surrounded by an invisible

try:
    <script here>
except: # plain except catches everything
    import traceback
    traceback.print_exc()

The idea behind this is that you can put all your error-handling in one place:

def main():
    try:
        data = read_data_files()
    except IOError, e:
        print 'There was a problem reading data:', e
        print 'Faking some up...'
        data = fake_data()
    ...

def read_data_files():
    r = []
    for fname in glob.glob('*.data'):
        r.extend(open(fname).readlines())
    f = open('index')
    return index_data(parse_index(f), r)


So if any of the open()s fails, or if a readlines() fails because of a corrupt
disk or something, the code will properly report and handle it.  You don't
need to surround each function that could possibly have a problem with error
handling code, and you don't have to worry that maybe you forgot one.  And you
don't have to figure out some special value read_data_files() should return if
something it called failed.  Remember the rule of thumb: detect errors at a
low level, handle them at a high level.  If read_data_files() did its own
error handling and exited whenever there was a problem, it would become less
reusable because the calling function might want to decide whether 'no data
files' is fatal or not.  If read_data_files() didn't do its own
error-handling, but didn't return a special value, well, that could be bad.
And if read_data_files() did error checking and did return a special value...
well, that's an exception, and python does that for you.

And exceptions aren't just limited to errors, they work nicely for many
exceptional situations.  Suppose you have a list of things to discuss with the
user and deal with.  At any point in the discussion, the user can decide to
skip this item and handle it later.  The discussion can get highly involved,
invoking many function which invoke other functions, but if the user decides
to skip it, you have to jump all the way back up out of the conversation:

class SkipItem(Exception):
    def __init__(self, reason_skipped):
        self.reason_skipped = reason_skipped

def deal_with_issues(issues):
    issues_left = []
    for item in issues:
        try:
            what_to_do = discuss(item)
            what_to_do.do_it()
        except SkipItem, e:
            print 'Skipping', item
            issues_left.append((item, e.reason_skipped))
    return issues_left

def discuss(item):
    ...
    <lotsa code>
    ...


And if unhandled, exceptions simply provide nice automatic error messages, as
you noted.

Another fun trick is to wrap your long-running python program in:

try:
    main()
except:
    exc = sys.exc_inf()
    mail_error(exc) # tell me the program crashed
    # or beep a pager if I have one and enjoy getting beeped at every
    # hour of the day, or turn on the siren and red strobe lights
    # downstairs, or...
    dump_state(exc) # save variables to a file for autopsy
    # it would be nice if one could simply pickle the program and later open
    # it up with pdb...
    sys.exit(1)



More information about the Python-list mailing list