-
-
Notifications
You must be signed in to change notification settings - Fork 34.1k
Description
This problem only appears when __getattr__ is implemented:
EDIT: Updated MWE
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!Expectation
The traceback should include "AttributeError: 'Index' object has no attribute 'iloc". The issue seems to be that object.__getattribute__ does not re-raise the AttributeError in case the fallback __getattr__ fails as well. According to the documentation, one might expect attribute lookup to work like:
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 attrBut instead, the original error gets swallowed if __getattr__ fails as well, i.e. it works more like:
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 attrThis can be problematic, because:
- Attribute lookup can fail due to actual bugs that unintentionally raise
AttributeError, for example within the code of a@property - Attributes/Properties might only exist conditionally, and with the current behavior, as the traceback does not include the original
AttributeErrorin 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:
Traceback (most recent call last):
File "/home/rscholz/Projects/KIWI/tsdm/bugs/python/__getattr__attributeerror.py", line 25, in <module>
MWE_with_getattr().foo # AttributeError: MWE_with_getattr has no attribute foo!
^^^^^^^^^^^^^^^^^^^^^^
File "/home/rscholz/Projects/KIWI/tsdm/bugs/python/__getattr__attributeerror.py", line 17, in __getattr__
raise AttributeError(f"{self.__class__.__name__} has no attribute {key}!")
AttributeError: MWE_with_getattr has no attribute foo!
The problem with non-existent .iloc is never mentioned!
Old MWE
from functools import cached_property
class Foo:
def __getattr__(self, key):
if key == "secret":
return "cake"
raise AttributeError(f"{self.__class__.__name__} has no attribute {key}!")
class Bar(Foo):
@property
def prop(self) -> int:
raise AttributeError("Invisible Error message")
@cached_property
def cached_prop(self) -> int:
raise AttributeError("Invisible Error message")
filler: str = "Lorem_ipsum"
obj = Bar()
obj.prop # ✘ AttributeError: Bar has no attribute prop!
obj.cached_prop # ✘ AttributeError: Bar has no attribute cached_prop!Expectation
The traceback should include "Invisible Error message". If we replace the AttributeError with, say, ValueError the problem does not occur.
Actual Output
The code above produces the following output:
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[11], line 25
21 filler: str = "Lorem_ipsum"
24 obj = Bar()
---> 25 obj.prop # ✘ AttributeError: Bar has no attribute cached_prop!
Cell In[11], line 8, in Foo.__getattr__(self, key)
6 if key == "secret":
7 return "cake"
----> 8 raise AttributeError(f"{self.__class__.__name__} has no attribute {key}!")
AttributeError: Bar has no attribute prop!
Your environment
- python 3.11.3
- Ubuntu 22.04