Why exceptions shouldn't be used for flow control [Re: YAS to the "Reading line-by-line" Problem]

Greg Ewing greg.ewing at compaq.com
Thu Jun 24 18:19:05 EDT 1999


William Tanksley wrote:
> 
> "Not exceptional enough".  What does that mean?  (I ain't never had too
> much exception! ;-)

Sorry, I was using "exceptional" in a non-technical sense there.
I meant "not abnormal enough".

I don't agree that reaching EOF is necessarily "abnormal".
If I want to read all the data in a file, and I don't know
in advance how much there is, I write a loop that keeps reading
until EOF. In that case, reaching EOF is not abnormal at all -
it's bound to happen eventually!

On the other hand, if I have reason to believe that there
should be a certain amount of data left, and I hit EOF while
trying to read it, then that is certainly an error, and
raising an exception is a reasonable thing to do.

The question, then, is what a general-purpose library routine
should do on EOF, given that the "right thing" depends on the
circumstances. You would have it always throw an exception,
and require the programmer to catch it if it is an "expected"
exception.

Personally, I prefer it the way it is. If I want an exception
thrown, I can always write a wrapper which does so. Doing it
the other way around - wrapping exception-throwing code into
something which doesn't - is trickier to get right; as I've
shown, it's hard to make sure that it catches only what you
want to catch.

> In addition, the complexity of the failure required to produce this is
> pretty high.  How many files do you need to read to get the next line from
> this file?

It's perhaps not the best example of this kind of problem.
A better one, which I actually have experienced, is from the
days when it was common to catch KeyError as a way of telling
when a key wasn't in a dictionary:

   try:
     v = d[the_key()]
   except KeyError:
     v = some_default_value

The subtle problem with that piece of code is that if there
is a bug which causes the_key() to raise a KeyError (which I
think you'll agree is *not* an unlikely event) it gets
incorrectly caught instead of triggering a traceback.
To guard against that, you have to be very careful what
you put inside the try:

   i = the_key()
   try:
     v = d[i]
   except KeyError:
     v = some_default_value

This is one of the reasons that the get() method was
added to dictionaries. Using it, you can write

   v = d.get(the_key(), some_default_value)

which is not only less error-prone but shorter and
clearer as well.

> If someone else's uncaught EOFError hits this function, something nasty is
> almost always going to happen... if it fails, it does so loudly so that
> the tester notices and reports the problem.

But that's exactly what *doesn't* happen! The misdirected exception
silently terminates a loop that it wasn't meant to terminate, and
later on the program fails with some set of symptoms that give little
or no clue as to what the original cause was. I would much rather get 
a traceback pinpointing exactly what was thrown and where it was thrown 
from.

> C is famous for noiselessly casting the -1 to a char (look at
> the old Bash security bug), and Python is even worse -- any return you can
> possibly make will almost certainly fit in noiselessly with anything you
> do with it.

The design of read() is perhaps not the best here -- it might be
better to return None, which would cause most things expecting a
string to blow up rather more quickly.

> You ought to feel lucky.  Some respected programming gurus recommend
> calling abort() on any odd results

Raising an exception is Python's equivalent of calling abort(). But
it only works as such if you can be sure that your exception isn't
going to be caught by something that makes unwarranted assumptions
about its cause. The more you use exceptions for "normal" things,
such as EOF or KeyError or IndexError when they aren't really errors, 
the more likely that is to happen. In my experience, anyway.

Greg




More information about the Python-list mailing list