Pythonic API design: detailed errors when you usually don't care

Steve Holden steve at holdenweb.com
Mon Oct 2 14:01:25 EDT 2006


Steve Holden wrote:
> Simon Willison wrote:
> 
>>Hi all,
>>
>>I have an API design question. I'm writing a function that can either
>>succeed or fail. Most of the time the code calling the function won't
>>care about the reason for the failure, but very occasionally it will.
>>
>>I can see a number of ways of doing this, but none of them feel
>>aesthetically pleasing:
>>
>>1.
>>
>>try:
>>  do_something()
>>except HttpError:
>>  # An HTTP error occurred
>>except ApplicationError:
>>  # An application error occurred
>>else:
>>  # It worked!
>>
>>This does the job fine, but has a couple of problems. The first is that
>>I anticipate that most people using my function won't care about the
>>reason; they'll just want a True or False answer. Their ideal API would
>>look like this:
>>
>>if do_something():
>>  # It succeeded
>>else:
>>  # It failed
>>
>>The second is that the common path is success, which is hidden away in
>>the 'else' clause. This seems unintuitive.
>>
>>2.
>>
>>Put the method on an object, which stores the reason for a failure:
>>
>>if obj.do_something():
>>  # It succeeded
>>else:
>>  # It failed; obj.get_error_reason() can be called if you want to know
>>why
>>
>>This has an API that is closer to my ideal True/False, but requires me
>>to maintain error state inside an object. I'd rather not keep extra
>>state around if I don't absolutely have to.
>>
>>3.
>>
>>error = do_something()
>>if error:
>>  # It failed
>>else:
>>  # It succeeded
>>
>>This is nice and simple but suffers from cognitive dissonance in that
>>the function returns True (or an object evaluating to True) for
>>failure.
>>
>>4.
>>
>>The preferred approach works like this:
>>
>>if do_something():
>>  # Succeeded
>>else:
>>  # Failed
>>
>>BUT this works too...
>>
>>ok = do_something()
>>if ok:
>>  # Succeeded
>>else:
>>  # ok.reason has extra information
>>  reason = ok.reason
>>
>>This can be implemented by returning an object from do_something() that
>>has a __nonzero__ method that makes it evaluate to False. This solves
>>my problem almost perfectly, but has the disadvantage that it operates
>>counter to developer expectations (normally an object that evaluates to
>>False is 'empty').
>>
>>I know I should probably just pick one of the above and run with it,
>>but I thought I'd ask here to see if I've missed a more elegant
>>solution.
>>
>>Thanks,
>>
>>Simon
>>
> 
> I should have thought the simplest spelling of your requirements would be
> 
> try:
>      do_something()
>      print "It worked"
> except:
>      # it didn't
> 
> However there are good reasons why this pattern isn't often recommended: 
> it masks types of exception that you may not have anticipated.
> 
The recipe for only catching known errors but giving them all the same 
response would be

try:
     do_something()
     print "It worked!"
except (ThisError, ThatError, TheOtherError):
     print "It failed!"

regards
  Steve
-- 
Steve Holden       +44 150 684 7255  +1 800 494 3119
Holden Web LLC/Ltd          http://www.holdenweb.com
Skype: holdenweb       http://holdenweb.blogspot.com
Recent Ramblings     http://del.icio.us/steve.holden




More information about the Python-list mailing list