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

Kay Schluehr kay.schluehr at gmx.net
Mon Oct 2 22:50:55 CEST 2006


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

As I see it this can become an architectural concern. If callers care
"occasionally" which precision or care is actually required? Is it just
that a developer wants to see a stack-trace for debugging purposes?
Here logging might be adaequate while otherwise if application logics
is dependent it might be helpfull to define a contract between caller
and callee. This could be a handler that is passed by the caller to
do_something() that must be aware of it by defining an optional
parameter in its interface. This is definitely framework overhead and
although I wouldn't qualify it as "ugly" it makes your program more
complex and dependent on more components. Finally you have to balance
the tradeoff. All other approaches I've seen here tried to tame the
effect of throwing exceptions that were only needed occasionally. It's
like dealing with unwanted checked exceptions in Java...




More information about the Python-list mailing list