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

Simon Willison swillison at gmail.com
Mon Oct 2 18:45:38 CEST 2006


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




More information about the Python-list mailing list