__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