A descriptor that is raising AttributeError in __get__() causes that the Python's interpreter continues searching for attributes in __mro__ calling __getattr__() function in inherited classes.
Let's take a look for example script with this bug.
class A1:
def __getattr__(self, name):
print("A1 visited")
raise AttributeError(f"{self.__class__.__name__}: {name} not found.")
class A2(A1):
def __getattr__(self, name):
print("A2 visited")
super().__getattr__(name)
class A3(A2):
def __getattr__(self, name):
print("A3 visited")
super().__getattr__(name)
class B:
def __init__(self, f):
self.f = f
def __get__(self, obj, objtype=None):
raise AttributeError("Python bug?")
class C(A3):
@B
def test(self):
return 25
B is a decorator attached to C.test() and it is throwing AttributeError. When c.test() is performed it starts for walking through C.__mro__ and calling __getattr__() function in objects.
>>> from bug import C
>>> c = C()
>>> c.test()
A3 visited
A2 visited
A1 visited
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/blooser/python-bug/bug.py", line 17, in __getattr__
super().__getattr__(name)
File "/home/blooser/python-bug/bug.py", line 11, in __getattr__
super().__getattr__(name)
File "/home/blooser/python-bug/bug.py", line 6, in __getattr__
raise AttributeError(f"{self.__class__.__name__}: {name} not found.")
AttributeError: C: test not found.
Changing error in B.__get__() to NameError:
class B:
def __init__(self, f):
self.f = f
def __get__(self, obj, objtype=None):
raise NameError("Python bug?")
causes it omits C.__mro__.
>>> from bug import C
>>> c = C()
>>> c.test()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/blooser/python-bug/bug.py", line 26, in __get__
raise NameError("Python bug?")
NameError: Python bug?
I'm thinking that it is expected behavior or is this a bug?
|