-
-
Notifications
You must be signed in to change notification settings - Fork 30.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
__getattribute__
does not propagate AttributeError
error-message if __getattr__
fails as well
#103936
Comments
To avoid this, I think try:
val = self.func(instance)
except Exception as E:
raise RuntimeError("Property evaluation failed!") from E instead of the plain Line 992 in a5308e1
|
Not sure if this is a "bug". The doc clearly states that |
@gaogaotiantian The problem is that the error message doesn't show in the traceback, which makes it difficult to debug. I wrote this after spending quite some time debugging, because I thought there was a problem with my |
The try:
obj.prop()
except AttributeError:
Foo._getattr__(obj, "prop") I believe the original design for this is for a "non-exist" attribute, not an Your proposal is a breaking change, which would affect code that actually taking advantage of this behavior. For example, I might want property methods that make the attribute "appear to be non-existent" so that it could be handled as a missing attribute. class A:
@property
def data(self):
if <condition>:
return ...
else:
raise AttributeError() |
This is a long-existing and fully documented behavior from before Python 3.5. Do not raise AttributeError then. |
In my case, there was an actual bug in my code, I wasn't intentionally raising an But even when it is intentional, like in the example @gaogaotiantian gave: class A:
@property
def data(self):
if <condition>:
return ...
else:
raise AttributeError("Failed to load property due to <condition>") Wouldn't it make sense to include the message
I believe both you and @gaogaotiantian have misunderstood the point of this issue. The issue is not about how Python's mechanism for looking up attributes works, the issue is that if that mechanism fails, not all relevant error messages are included in the traceback! |
Here is an updated MWE to more clearly demonstrate the issue: import pandas as pd
class MWE:
@property
def foo(self):
s = pd.Index([1,2,3])
return s.iloc[0] # actual bug! pd.Index has no .iloc attribute
MWE().foo # AttributeError: 'Index' object has no attribute 'iloc'
# without `__getattr__` implemented we get the right error message!
class MWE_with_getattr:
def __getattr__(self, key):
if key == "dummy":
return "xyz"
raise AttributeError(f"{self.__class__.__name__} has no attribute {key}!")
@property
def foo(self):
s = pd.Index([1,2,3])
return s.iloc[0] # actual bug! pd.Index has no .iloc attribute
MWE_with_getattr().foo # AttributeError: MWE_with_getattr has no attribute foo!
# Traceback never mentions "AttributeError: 'Index' object has no attribute 'iloc'"
# Implementing `__getattr__` gobbles up the error message! |
if it succeeded, then there is no way to indicate this failure of property access. |
That's outside the scope of this issue. |
@randolf-scholz , first of all, we should all agree that this is not a bug report anymore, this is a feature request now. The code behaves as expected and documented, what you want is something more - a better traceback message. I agree that in your case, being able to know where the attribute error originated would be helpful. The original improvement of yours (reraising as a different exception) was a breaking change (the code I gave would behave differently) so I don't think that would happen. Is it possible to make I don't think it's worth it to complicate a more common use case, just to make a rare one better. In this specific case, I think the right path is: @property
def foo(self):
try:
s = pd.Index([1,2,3])
return s.iloc[0] # actual bug! pd.Index has no .iloc attribute
except AttributeError as exc:
raise RuntimeError() from exc Or, write your own wrapper for properties. You may argue that the user would have to know that the code in the method could potentially raise |
I don't see any problem in that, and in fact, it can be a very useful thing as your own example demonstrates: class A:
def __getattr__(self, key):
try:
attr = self._fallback(key)
except Exception as E:
raise AttributeError(f"{self} has no attribute {key}!") from E
else:
return attr
@property
def data(self):
if <condition>:
return ...
else:
raise AttributeError("Failed to load property due to <condition>") With current behavior, if |
This is in the doc.
I think |
I don't see it explicitly described there. When I read the relevant passage
My intuitive expectation would be that the attribute-lookup roughly works as: class object:
def __getattribute__(self, key):
try:
attr = self._default_attribute_lookup(key)
except AttributeError as E:
try: # alternative lookup via __getattr__
attr = self.__getattr__(key)
except Exception as F:
raise F from E # re-raise if __getattr__ fails as well
else:
return attr
else:
return attr But instead, the original error gets swallowed if class object:
def __getattribute__(self, key):
try:
attr = self._default_attribute_lookup(key)
except AttributeError as E:
# if __getattr__ fails, we are left in the dark as to why default lookup failed.
return self.__getattr__(key)
else:
return attr |
property
shows no error Traceback if AttributeError
is raised during evaluation__getattribute__
does not propagate AttributeError
error-message if __getattr__
fails as well
I can imagine that possibly the nested |
I think this is a valid and reasonable feature request. There may be reasons not to do it that would become clear in attempting to implement it, but I don't see a priori reasons to dismiss the feature. The only real objection to the chaining that has been raised so far is this one:
But I don't think this is a strong objection. In this case, the newly-chained exception would accurately reflect the situation: a normal attribute access failed, |
I agree with @carljm. This isn't a bug, and the the original suggestion of wrapping the |
Sure, we can try to build a prototype and see if breaks anything unexpected. |
Just encountered this again, after some very painful debugging I found https://stackoverflow.com/questions/36575068 and remembered that I had this issue before. user2483412 Provided a good workaround: def __getattr__(self, name):
if name == 'abc':
return 'abc'
return self.__getattribute__(name) instead of def __getattr__(self, name):
if name == 'abc':
return 'abc'
raise AttributeError I still think that the re-raising should, because this is incredibly frustrating to debug. One could even add a note to consider this implementation. |
I would be wary of this approach, as it basically means the attribute access flow is In the bug, during the first attribute access, the property would check if an instance attribute is already set, and if it was, it would be immediately returned. Otherwise, it would run a function to create the value for that attribute. If that function raised an exception, the instance attribute would be set to an empty value and the error would be re-raised. Below is a minimal example: class Request:
_data = None
@property
def data(self):
if self._data is None:
self.load_data()
return self._data
def load_data(self):
try:
self._data = do_something()
except:
self._data = {}
raise Now with the flow mentioned above, if an |
This problem only appears when
__getattr__
is implemented:EDIT: Updated MWE
Expectation
The traceback should include
"AttributeError: 'Index' object has no attribute 'iloc"
. The issue seems to be thatobject.__getattribute__
does not re-raise theAttributeError
in case the fallback__getattr__
fails as well. According to the documentation, one might expect attribute lookup to work like:But instead, the original error gets swallowed if
__getattr__
fails as well, i.e. it works more like:This can be problematic, because:
AttributeError
, for example within the code of a@property
AttributeError
in case the fallback__getattr__
fails as well, the error message explaining the conditional failure is not passed along.Actual Output
The code above produces the following output:
The problem with non-existent
.iloc
is never mentioned!Old MWE
Expectation
The traceback should include
"Invisible Error message"
. If we replace theAttributeError
with, say,ValueError
the problem does not occur.Actual Output
The code above produces the following output:
Your environment
The text was updated successfully, but these errors were encountered: