Exception Handling Practices / Patterns

Steven D'Aprano steve+comp.lang.python at pearwood.info
Sat Aug 24 06:27:45 EDT 2013


On Fri, 23 Aug 2013 22:25:55 -0700, snarf wrote:

[...]
> * Seems like exception handing within Classes is largely avoided and is
> typically only used when calling external libraries. 

There is certainly no rule "avoid exceptions inside classes". Methods 
often raise exceptions to signal an error, e.g.:

"My string".index("spam")


Less common, methods can raise exceptions as part of their flow control. 
The most obvious example is StopIteration, used by iterators and often by 
__iter__ or next methods.


> * Try/Except type
> statements seem to be used more within modules, main functions, wrapper
> scripts. 

It depends on whose code you are reading. I don't write a lot of classes, 
but when I do, I often use try...except inside them.

If try...except gets used more frequently in module's top level, it is 
because the sort of things that you do at the top level often needs 
exception handling. For example, you might have a fallback module:

try:
    import this_module
except ImportError:
    import that_module as this_module


You will very rarely see that inside a class, since you very rarely 
import modules inside a class.


> * Classes should be coded in a way that exceptions 

I think you forgot to finish the sentence.


> * Better to
> never write your own exceptions (unless you absolutely have to). 

That depends.

On the one hand, nobody wants a million different exception types. On the 
other hand, nobody wants just *one* exception type, and no way to 
distinguish between different kinds of errors. Somewhere between one and 
one million is an appropriate number of exception types.

The right answer is to be conservative about creating new exceptions, but 
don't be scared to create one when you need one.

But when you do, it is often better to subclass from an appropriate built-
in exception like ValueError or TypeError or similar, than to subclass 
from Exception itself.


> * Using
> Exception is typically a bad. More specific the better.

Yes, you should always try to catch the specific exceptions you care 
about:


# Best
except ValueError, OverflowError, ZeroDivisionError:


# Not so good
except Exception:


# Worst
except:


Don't use the last one, except maybe in the interactive interpreter, 
since it will catch *everything*, even exceptions that probably shouldn't 
be caught like KeyboardInterrupt.


> * Exceptions
> should never fail silently. (Should exceptions always be logged?)

Certainly not. Exceptions should fail silently if you don't care about 
them. For example, when connecting to a website, there are many temporary 
errors that can occur. The best way to handle them is to catch the 
exception, sleep for a little while, then try again. You need only care 
if repeated attempts to connect, with larger and larger sleeps, continue 
to fail.

Of course, you might have a debug mode that logs all of these, but if 
your web browser logged every single time a webpage was slow to respond, 
you would soon run out of disk space :-)

*Errors* should never fail silently, unless explicitly silenced. But an 
error isn't an error if you don't care about it, and an exception is not 
necessarily an error.

This is an error, because converting a number to uppercase cannot 
possibly mean anything:

mystring = 42
mystring.upper()


This is not necessarily an error, since "the list is empty" could be a 
legitimate situation:

mylist = []
first = mylist[0]

In this case, it may be appropriate to catch the exception, and either 
silently swallow it, or do something else.


> Best site I have found for exceptions (hopefully this helps someone): *
> http://c2.com/cgi/wiki?ExceptionPatterns

I haven't read that page for a long time, but as I recall the c2.com 
website, a lot of the ideas there are better suited to Java and C/C++ 
(and occasionally Lisp) rather than Python. But still, a valuable (if 
often confusing) resource.



> I'd be interested in hearing others thoughts on this topic with regards
> to best practices for when to use exceptions, and when to avoid using
> exceptions.

The try part of a try...except is *very* fast to set up. It's about as 
fast as a "pass" (do nothing), so it has little overhead.

On the other hand, actually *catching* an exception is quite heavy. So 
code that catches lots and lots of exceptions may be slow. In that case, 
it may be faster to "look before you leap" and test ahead of time:

# this is faster if most lists are not empty
try:
    process(mylist[0])
except IndexError:
    handle_empty_list()

# this is faster if many lists are empty
if mylist:
    process(mylist[0])
else:
    handle_empty_list()


Only profiling your data can tell you which you should use.


On the other hand, here you should *always* use try...except:

try:
    myfile = open("name")
except IOError:
    handle_error()


Because this code is wrong:

if os.path.exists("name"):
    myfile = open("name")
else:
    handle_error()


It's wrong for a couple of reasons:

- just because the file exists, doesn't mean you can open it;

- even if the file exists when you call the os.path.exists function, 
doesn't mean it will still exist a millisecond later when you try to open 
it.



Hope this is helpful,




-- 
Steven



More information about the Python-list mailing list