Better to 'try' something and catch the exception or test if it's possible first to avoid an exception?

You should prefer try/except over if/else if that results in

  • speed-ups (for example by preventing extra lookups)
  • cleaner code (fewer lines/easier to read)

Often, these go hand-in-hand.


speed-ups

In the case of trying to find an element in a long list by:

try:
    x = my_list[index]
except IndexError:
    x = 'NO_ABC'

the try, except is the best option when the index is probably in the list and the IndexError is usually not raised. This way you avoid the need for an extra lookup by if index < len(my_list).

Python encourages the use of exceptions, which you handle is a phrase from Dive Into Python. Your example not only handles the exception (gracefully), rather than letting it silently pass, also the exception occurs only in the exceptional case of index not being found (hence the word exception!).


cleaner code

The official Python Documentation mentions EAFP: Easier to ask for forgiveness than permission and Rob Knight notes that catching errors rather than avoiding them, can result in cleaner, easier to read code. His example says it like this:

Worse (LBYL 'look before you leap'):

#check whether int conversion will raise an error
if not isinstance(s, str) or not s.isdigit():
    return None
elif len(s) > 10:    #too many digits for int conversion
    return None
else:
    return int(s)

Better (EAFP: Easier to ask for forgiveness than permission):

try:
    return int(s)
except (TypeError, ValueError, OverflowError): #int conversion failed
    return None

In this particular case, you should use something else entirely:

x = myDict.get("ABC", "NO_ABC")

In general, though: If you expect the test to fail frequently, use if. If the test is expensive relative to just trying the operation and catching the exception if it fails, use try. If neither one of these conditions applies, go with whatever reads easier.


Using try and except directly rather than inside an if guard should always be done if there is any possibility of a race condition. For example, if you want to ensure that a directory exists, do not do this:

import os, sys
if not os.path.isdir('foo'):
  try:
    os.mkdir('foo')
  except OSError, e
    print e
    sys.exit(1)

If another thread or process creates the directory between isdir and mkdir, you'll exit. Instead, do this:

import os, sys, errno
try:
  os.mkdir('foo')
except OSError, e
  if e.errno != errno.EEXIST:
    print e
    sys.exit(1)

That will only exit if the 'foo' directory can't be created.


If it's trivial to check whether something will fail before you do it, you should probably favor that. After all, constructing exceptions (including their associated tracebacks) takes time.

Exceptions should be used for:

  1. things that are unexpected, or...
  2. things where you need to jump more than one level of logic (e.g. where a break doesn't get you far enough), or...
  3. things where you don't know exactly what is going to be handling the exception ahead of time, or...
  4. things where checking ahead of time for failure is expensive (relative to just attempting the operation)

Note that oftentimes, the real answer is "neither" - for instance, in your first example, what you really should do is just use .get() to provide a default:

x = myDict.get('ABC', 'NO_ABC')

As the other posts mention, it depends on the situation. There are a few dangers with using try/except in place of checking the validity of your data in advance, especially when using it on bigger projects.

  • The code in the try block may have a chance to wreak all sorts of havoc before the exception is caught - if you proactively check beforehand with an if statement you can avoid this.
  • If the code called in your try block raises a common exception type, like TypeError or ValueError, you may not actually catch the same exception you were expecting to catch - it may be something else that raise the same exception class before or after even getting to the line where your exception may be raised.

e.g., suppose you had:

try:
    x = my_list[index_list[3]]
except IndexError:
    x = 'NO_ABC'

The IndexError says nothing about whether it occurred when trying to get an element of index_list or my_list.