-
-
Notifications
You must be signed in to change notification settings - Fork 29.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
getattr silences an unrelated AttributeError #84046
Comments
if a class has a descriptor and a defined __getattr__ method, and an AttributeError (unrelated to the descriptor lookup) is raised inside the descriptor, it will be silenced: class A:
@property
def myprop(self):
print("property called")
a = 1
a.foo # <-- AttributeError that should not be silenced
def __getattr__(self, attr_name):
print("__getattr__ called")
a = A()
a.myprop In this example myprop() is called, the error silenced, then __getattr__() is called. |
As unfortunate as this is, I don't think there's an easy way to solve this while adhering to descriptors and the attribute lookup model. This is a consequence of the following rule:
As it notes, if the __get__() raises an AttributeError then a fallback to __getattr__ is initiated. One may think that maybe we can just catch AttributeError in @Property to try to fix this problem but a quick search shows that people do intentionally raise AttributeError in @Property methods: While this in combination with a __getattr__ is rare, I was able to find one example: I don't think that changing this behavior is acceptable as people might be relying on it and it's well documented. In your case, I think it's really the "catch-all" __getattr__ that's at fault here which really shouldn't be returning None for all attributes blindly. This does bring up a good point though that in the process of this fall-back behavior, the original AttributeError from A's property does get masked. What can be done and might be a good idea is to show the original AttributeError failure as the cause for the second. Something like this: Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "<stdin>", line 5, in myprop
AttributeError: 'int' object has no attribute 'foo'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<stdin>", line 7, in <module>
File "<stdin>", line 5, in <module>
File "<stdin>", line 7, in __getattr__
AttributeError: not found in __getattr__ This at least somewhat indicates that the original descriptor __get__ failed and then __getattr__ also raised. As opposed to now where the original exception gets masked: >>> class A:
... @property
... def myprop(self):
... a = 1
... a.foo
... def __getattr__(self, attr_name):
... raise AttributeError("not found in __getattr__")
...
>>> a = A()
>>> a.myprop
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in __getattr__
AttributeError: not found in __getattr__ I'm gonna take a stab at implementing this real quick to see if it's actually useful and viable. |
Update: opened up #19303 as a quick first pass at implementing this. It works as expected and retains the context from the original exception just in case it's needed. The code isn't too pretty, looks like exception chaining was primarily designed to be a first class citizen using the |
My instincts are to leave this alone and not gum up heavily trafficked core business logic. I don't like the external calls, the extra increfs and decrefs (and possible rentrancy issues), performance impact, or the pattern of holding the exception across a call that can invoke arbitrary code. The code for slot_tp_getattr_hook() is currently very clean and it would be nice to keep it that way. ISTM that the problem is better addressed by unittesting, linting, and static type checking rather than by gumming-up runtime. |
Am going to mark this as closed for the reasons listed above. If another coredev wants to champion this, feel free to resurrect the issue. |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: