views:

421

answers:

7
+6  Q: 

Avoid exceptions?

This particular example relates to Django in Python, but should apply to any language supporting exceptions:

try:
    object = ModelClass.objects.get(search=value)
except DoesNotExist:
    pass

if object:
    # do stuff

The Django model class provides a simple method get which allows me to search for one and only one object from the database, if it finds more or less it raises an exception. If can find zero or more with an alternative filter method, which returns a list:

objects = ModelClass.objects.filter(search=value)
if len(objects) == 1:
    object = objects[0]
    # do stuff

Am I overly averse to exceptions? To me the exception seems a little wasteful, at a guess, a quarter-to-a-half of the time will be 'exceptional'. I'd much prefer a function that returns None on failure. Would I be better to use Django's filter method and process the list myself?

+5  A: 

The clue is in the name - exceptions should be exceptional.

If you always expect the item will exist then use get, but if you expect it not to exist a reasonable proportion of the time (i.e. it not existing is an expected result rather than an exceptional result) then I'd suggest using filter.

So, seeing as you indicated that between 1 in 2 and 1 in 4 are expected not to exist, I'd definitely write a wrapper around filter, as that's definitely not an exceptional case.

Greg Beech
+1: Exceptions are good -- use them. Just don't abuse them.
S.Lott
+2  A: 

The answer will depend on the intent of the code. (I'm not sure what your code sample was meant to do, the pass in the exceptional case is confusing, what will the rest of the code do with object variable to work with?)

Whether to use exceptions or to use a method which treat the case as non-exceptional is a matter of taste in many cases. Certainly if the real code in the except clause is as complicated as the filter method you'd have to use to avoid the exception, then use the filter method. Simpler code is better code.

Ned Batchelder
I'll revise the example.
Mat
+3  A: 

I agree with the other answer but I wanted to add that exception passing like this is going to give you a very noticeable performance hit. Highly recommended that you check to see if the result exists (if that's what filter does) instead of passing on exceptions.


Edit:

In response to request for numbers on this, I ran this simple test...

import time

def timethis(func, list, num):
  st=time.time()
  for i in xrange(0,1000000):
    try:
      func(list,num)
    except:
      pass
  et = time.time()
  print "Took %gs" % (et-st)

def check(list, num):
  if num < len(list):
    return list[num]
  else:
    return None

a=[1]
timethis(check, a, 1)
timethis(lambda x,y:x[y], a, 1)

And the output was..

Took 0.772558s
Took 3.4512s

HTH.

Jimmy2Times
Can you provide some measurement comparing the exception and non-exception versions? I'd like to see how large this "noticeable" hit really is.
S.Lott
For the record, both of those examples throw a StopIteration exception. IMO this shows that exceptions DON'T have a significant impact on performance if used sanely.
Jason Baker
a[1] will give you IndexError. The iteration keeps going because the exception is caught and passed in the loop. I maintain that my example is valid in showing that passed exceptions in a loop can be a performance hit (5x in this case).
Jimmy2Times
+1  A: 

Aversion to excpetions is a matter of opinion - however, if there's reason to believe that a function or method is going to be called many times or called rapidly, exceptions will cause a significant slowdown. I learned this from my previous question, where I was previously relying on a thrown exception to return a default value rather than doing parameter checking to return that default.

Of course, exceptions can still exist for any reason, and you shouldn't be afraid to use or throw one if necessary - especially ones that could potentially break the normal flow of the calling function.

Raymond Martineau
+5  A: 

There's a big schism in programming languages around the use of exceptions.

  • The majority view is that exceptions should be exceptional. In most languages with exceptions, transfer of control by exception is considerably more expensive than by procedure return, for example.

  • There is a strong minority view that exceptions are just another control-flow construct, and they should be cheap. The Standard ML of New Jersey and Objective Caml compilers subscribe to that view. If you have cheap exceptions you can code some fancy backtracking algorithms in ways that are more difficult to code cleanly using other mechanisms.

I've seen this debate repeated many times for new language designs, and almost always, the winner decides that exceptions should be expensive and rare. When you care about performanced, you'd be wise to program with this in mind.

Norman Ramsey
+7  A: 

Believe it or not, this actually is an issue that is a bit different in each language. In Python, exceptions are regularly thrown for events that aren't exceptional by the language itself. Thus I think that the "you should only throw exceptions under exceptional circumstances" rule doesn't quite apply. I think the results you'll get on this forum will be slanted towards that point of view though, considering the high number of .Net programmers (see this question) for more info on that).

At a very minimum, I'd better not catch anyone who sticks to that rule ever using a generator or a for loop in Python (both of which involve throwing exceptions for non-exceptional circumstances).

Jason Baker
+1: Python uses exceptions in some situations that are not anomalous. If it keeps it simple, simple is better than complex (i.e. raise StopIteration).
cdleary
+2  A: 

I disagree with the above comments that an exception is inefficient in this instance, especially since it's being used in an I/O bound operation.

Here's a more realistic example using Django with an in-memory sqlite database. Each of a 100 different queries was run, then averaged for each of a 100 runs. Although I doubt if it would matter, I also changed the order of execution.

With ObjectDoesNotExist... 0.102783939838
Without exception ........ 0.105322141647

With ObjectDoesNotExist... 0.102762134075
Without exception ........ 0.101523952484

With ObjectDoesNotExist... 0.100004930496
Without exception ........ 0.107946784496

You can instrument this in your own Django environment, but I doubt if your time is well spent avoiding this exception.

Jeff Bauer