This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Author ammar2
Recipients ammar2, pasenor, rhettinger
Date 2020-03-31.09:05:13
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1585645514.27.0.36111465286.issue39865@roundup.psfhosted.org>
In-reply-to
Content
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:

> object.__getattr__(self, name):
>   Called when the default attribute access fails with an
>   AttributeError (either __getattribute__() raises an AttributeError
>   because name is not an instance attribute or an attribute in the
>   class tree for self; or __get__() of a name property raises
>   AttributeError)

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:

* https://github.com/kdschlosser/EventGhost-TPLink/blob/a4a642fde8dd4deba66262a36d673cbbf71b8ceb/TPLink/tp_link/rule.py#L148-L152

* https://github.com/ajayau404/sniffer/blob/cd0c813b8b526a3c791735a41b13c7677eb4aa0e/lib/python3.5/site-packages/vpython/vpython.py#L1942-L1944

While this in combination with a __getattr__ is rare, I was able to find one example:

* https://github.com/xrg/behave_manners/blob/19a5feb0b67fe73cd902a959f0d038b905a69b38/behave_manners/context.py#L37

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.
History
Date User Action Args
2020-03-31 09:05:14ammar2setrecipients: + ammar2, rhettinger, pasenor
2020-03-31 09:05:14ammar2setmessageid: <1585645514.27.0.36111465286.issue39865@roundup.psfhosted.org>
2020-03-31 09:05:14ammar2linkissue39865 messages
2020-03-31 09:05:13ammar2create