Modern recommended exception handling practices?

Chris Angelico rosuav at gmail.com
Tue Nov 3 02:16:18 EST 2015


On Tue, Nov 3, 2015 at 5:47 PM, Steven D'Aprano <steve at pearwood.info> wrote:
> On Fri, 30 Oct 2015 04:43 am, vasudevram wrote:
>
>> Are there any modern (i.e. for current versions of Python 2 and 3)
>> recommended exception handling practices?
>
> When you say "modern", it sounds like you want to contrast them with "old
> fashioned and obsolete" exception-handling practices. I don't know if there
> are any obsolete exception-handling practices in Python.

Aside from string exceptions and the "except Type, e:" syntax, I would
agree with you. Actually, I can't think of any "obsolete
exception-handling practices" in any language. Exception handling is
pretty straight-forward: you raise an exception in one place, and you
catch it in another.

> Bare `except` clauses are very possibly *literally the worst* thing that you
> can write in Python:
>
> https://realpython.com/blog/python/the-most-diabolical-python-antipattern/

I would actually like to disallow the bare except, in the same way
that I would disallow the Py2 input() function. In the rare cases
where you really do want to catch *every* exception (eg at a boundary
between a web server and a request handler, where you would log the
exception and return a 500), it should be spelled "except
BaseException [as e]:", same as you should use "eval(raw_input())" in
those extremely rare cases where you actually want to take keyboard
input and evaluate it.

But more generally, the over-broad exception handler is a nasty anti-pattern.

> (5) Remember that often you can avoid exceptions instead of catching
> them. "Look Before You Leap" (LBYL) may be a perfectly good alternative:
>
>     if item in mylist:
>         idx = mylist.index(item)
>         process(idx)
>     else:
>         result = "not found"
>
>
> but be sensitive to the amount of work done. The above code searches the
> list twice instead of just once.

Not to mention having race condition possibilities. There are a few
places where this is useful, though:

start_time = time()
work_done = do_some_work()
time_spent = time()-start_time or 1
print(f"Did {work_done} jobs in {time_spent} secs: {work_done/time_spent} j/s")

The "or 1" is a quick check that means we don't divide by zero. The
performance figure becomes meaningless, but if this is a rare case,
that's probably fine.

> (7) So which is faster, LBYL or catching the exception? That is extremely
> sensitive to not just the specific operations being performed, but how
> often the exceptional cases occur. In general, you must measure your code
> to know.
>
> But as a very rough rule of thumb, consider looking up a key in a dict:
>
>     if key in mydict:
>         result = mydict[key]
>
> versus
>
>     try:
>         result = mydict[key]
>     except KeyError:
>         pass
>
>
> In my experience, catching the KeyError is about ten times more costly than
> testing for the key's presence. So if your keys are missing more than one
> time in ten, it's probably better to use LBYL.

This is actually a tribute to dict performance for key lookup (since
that's one of the important operations in a hashtable). It's NOT the
case for a list.


Exceptions are the "other way" to return something. In a function that
always returns a string, returning None is distinguishable (in the
same way that a C function can return a NULL pointer); in a function
that returns absolutely any object, the only way to signal "no object
to return" is to raise an exception. That's why StopIteration exists,
for instance. Exceptions are a normal part of program flow - they
signal an "exceptional condition" in some small area, but it's normal
to cope with exceptional conditions.

ChrisA



More information about the Python-list mailing list