__getattribute__'s error is not available in __getattr__

Chris Angelico rosuav at gmail.com
Tue May 2 22:24:39 EDT 2017


On Wed, May 3, 2017 at 10:59 AM, Jason Maldonis <jjmaldonis at gmail.com> wrote:
> Here's an example where the underlying error is completely hidden:
>
> class A(object):
>     def _some_complex_code_hidden_from_the_user(self):
>         # Run a bunch of complex stuff that raises an attribute error
> internally
>         # This could go layers deep into different modules
>         return self.this_doesnt_exist
>
>     @property
>     def x(self):
>         return self._some_complex_code_hidden_from_the_user()
>
>     def __getattr__(self, attr):
>         raise AttributeError("raised from A.__getattr__ to stop execution")
>
> a = A()
> print(a.x)
>
>
> This results in the following output:
>
> Traceback (most recent call last):
>   File "test3.py", line 17, in <module>
>     print(a.x)
>   File "test3.py", line 14, in __getattr__
>     raise AttributeError("raised from A.__getattr__ to stop execution")
> AttributeError: raised from A.__getattr__ to stop execution

AIUI, the problem here is that a property function that leaks an
AttributeError looks like a missing property and calls __getattr__. Is
that correct?

What you could do is put in a little guard decorator. Basically, mark
this function as "should never raise AttributeError":

def wont_raise(*exc):
    def deco(func):
        @functools.wraps(func)
        def wrapper(*a, **kw):
            try:
                return func(*a, **kw)
            except exc as e:
                raise RuntimeError("Faulty exception raised") from e
                # Note that in Py2, you may want manual exception chaining
                # to avoid losing the original (this uses Py3's __cause__).
        return wrapper
    return deco

class A:
    @property
    @wont_raise(AttributeError)
    def x(self):
        return self._x
    def __getattr__(self, attr):
        raise AttributeError("raised from A.__getattr__ to stop execution")

>>> A().x
Traceback (most recent call last):
  File "<stdin>", line 6, in wrapper
  File "<stdin>", line 5, in x
  File "<stdin>", line 7, in __getattr__
AttributeError: raised from A.__getattr__ to stop execution

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 8, in wrapper
RuntimeError: Faulty exception raised

You now have the entire exception traceback.

ChrisA



More information about the Python-list mailing list