[Python-ideas] Improving Catching Exceptions
Rob Cliffe
rob.cliffe at btinternet.com
Sun Jun 25 20:06:43 EDT 2017
On 24/06/2017 11:03, Steven D'Aprano wrote:
> On Sat, Jun 24, 2017 at 01:02:55PM +1200, Greg Ewing wrote:
>
>> In any case, this doesn't address the issue raised by the OP,
>> which in this example is that if the implementation of
>> bah.__getitem__ calls something else that raises an IndexError,
>> there's no easy way to distinguish that from one raised by
>> bah.__getitem__ itself.
> I'm not convinced that's a meaningful distinction to make in general.
> Consider the difference between these two classes:
>
> class X:
> def __getitem__(self, n):
> if n < 0:
> n += len(self)
> if not 0 <= n < len(self):
> raise IndexError
> ...
>
> class Y:
> def __getitem__(self, n):
> self._validate(n)
> ...
> def _validate(self, n):
> if n < 0:
> n += len(self)
> if not 0 <= n < len(self):
> raise IndexError
>
>
> The difference is a mere difference of refactoring. Why should one of
> them be treated as "bah.__getitem__ raises itself" versus
> "bah.__getitem__ calls something which raises"? That's just an
> implementation detail.
>
> I think we're over-generalizing this problem. There's two actual issues
> here, and we shouldn't conflate them as the same problem:
>
> (1) People write buggy code based on invalid assumptions of what can and
> can't raise. E.g.:
>
> try:
> foo(baz[5])
> except IndexError:
> ... # assume baz[5] failed (but maybe foo can raise too?)
>
>
> (2) There's a *specific* problem with property where a bug in your
> getter or setter that raises AttributeError will be masked, appearing as
> if the property itself doesn't exist.
>
>
> In the case of (1), there's nothing Python the language can do to fix
> that. The solution is to write better code. Question your assumptions.
> Think carefully about your pre-conditions and post-conditions and
> invariants. Plan ahead. Read the documentation of foo before assuming
> it won't raise. In other words, be a better programmer.
>
> If only it were that easy :-(
>
> (Aside: I've been thinking for a long time that design by contract is a
> very useful feature to have. It should be possibly to set a contract
> that states that this function won't raise a certain exception, and if
> it does, treat it as a runtime error. But this is still at a very early
> point in my thinking.)
>
> Python libraries rarely give a definitive list of what exceptions
> functions can raise, so unless you wrote it yourself and know exactly
> what it can and cannot do, defensive coding suggests that you assume any
> function call might raise any exception at all:
>
> try:
> item = baz[5]
> except IndexError:
> ... # assume baz[5] failed
> else:
> foo(item)
>
>
> Can we fix that? Well, maybe we should re-consider the rejection of PEP
> 463 (exception-catching expressions).
>
> https://www.python.org/dev/peps/pep-0463/
I'm all in favour of that :-) but I don't see how it helps in this example:
try:
item = (baz[5] except IndexError: SomeSentinelValue)
if item == SomeSentinelValue:
... # assume baz[5] failed
else:
foo(item)
is clunkier than the original version. Or am I missing something? Only
if the normal and exceptional cases could be handled the same way would
it help:
foo(baz[5] except IndexError: 0)
Rob Cliffe
>
>
> Maybe we need a better way to assert that a certain function won't raise
> a particular exception:
>
> try:
> item = bah[5]
> without IndexError:
> foo(item)
> except IndexError:
> ... # assume baz[5] failed
>
> (But how is that different from try...except...else?)
>
>
>
> In the case of (2), the masking of bugs inside property getters if they
> happen to raise AttributeError, I think the std lib can help with that.
> Perhaps a context manager or decorator (or both) that converts one
> exception to another?
>
> @property
> @bounce_exception(AttributeError, RuntimeError)
> def spam(self):
> ...
>
>
> Now spam.__get__ cannot raise AttributeError, if it does, it will be
> converted to RuntimeError. If you need finer control over the code that
> is guarded use the context manager form:
>
> @property
> def spam(self):
> with bounce_exception(AttributeError, RuntimeError):
> # guarded
> if condition:
> ...
> # not guarded
> raise AttributeError('property spam doesn't exist yet')
>
>
>
More information about the Python-ideas
mailing list