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




_______________________________________________
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to