[Python-ideas] Improving Catching Exceptions

Greg Ewing greg.ewing at canterbury.ac.nz
Sat Jun 24 08:31:40 EDT 2017


Steven D'Aprano wrote:
> 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
> 
> 
> Why should one of 
> them be treated as "bah.__getitem__ raises itself" versus 
> "bah.__getitem__ calls something which raises"?

They shouldn't be treated differently -- they're both legitimate
ways for __getitem__ to signal that the item doesn't exist.

What *should* be treated differently is if an IndexError occurs
incidentally from something else that __getitem__ does. In
other words, Y's __getitem__ should be written something like

    def __getitem__(self, n):
       self.validate(n)
       # If we get an IndexError from here on, it's a bug
       try:
          # work out the result and return it
       except IndexError as e:
          raise RuntimeError from e

> 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.:
> 
> (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.

Agreed.

Case 1 can usually be handled by rewriting the code so as to
make the scope of exception catching as narrow as possible.

Case 2 needs to be addressed within the method concerned on a
case-by-case basis. If there's a general principle there, it's
something like this: If you're writing a method that uses
an exception as part of it's protocol, you should catch any
incidental occurrences of the same exception and reraise it
as a different exception.

I don't think there's anything more the Python language could do
to help with either of those.

> (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.)

That sounds dangerously similar to Java's checked exceptions,
which has turned out to be a huge nuisance and not very
helpful.

> 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?)

It's no different, if I understand what it's supposed to mean
correctly.

> @property
> @bounce_exception(AttributeError, RuntimeError)
> def spam(self):
>      ...

In the case of property getters, it seems to me you're almost
always going to want that functionality, so maybe it should
be incorporated into the property decorator itself.

The only cases where you wouldn't want it would be if your
property dynamically figures out whether it exists or not,
and in those rare cases you would just have to write your
own descriptor class.

-- 
Greg



More information about the Python-ideas mailing list