Reputation: 316
Refer to the second top answer to an existing question: Difference between __getattr__
vs __getattribute__
, which including code suggested by someone:
class Count(object):
def __init__(self, mymin, mymax):
self.mymin = mymin
self.mymax = mymax
self.current = None
def __getattr__(self, item):
self.__dict__[item] = 0
return 0
def __getattribute__(self, item):
if item.startswith('cur'):
raise AttributeError
return super(Count, self).__getattribute__(item)
obj1 = Count(1, 10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.current)
My question is:
When I run the code, it did not run into an infinite recursion deep (by ending with maximum recursion depth exceeded). Why? And, if I change the code super(Count, self).__getattribute__(item)
to super(object, self).__getattribute__(item)
, it did run into an infinite loop. Why again?
Please explain the reason with a detailed calling process.
Upvotes: 2
Views: 662
Reputation: 133849
I will try to make it simpler by breaking the self.__dict__[item]
into 2 parts:
class Count(object):
def __getattr__(self, item):
print('__getattr__:', item)
d = self.__dict__
print('resolved __dict__')
d[item] = 0
return 0
def __getattribute__(self, item):
print('__getattribute__:', item)
if item.startswith('cur'):
raise AttributeError
return super(Count, self).__getattribute__(item)
obj1 = Count()
print(obj1.current)
The output is
__getattribute__: current
__getattr__: current
__getattribute__: __dict__
resolved __dict__
0
Now, if we replace super(Count, self)
with the incorrect construct super(object, self)
the message is not printed. It is because __getattribute__
will also mask the access to __dict__
. However the super
object will point to the base class of object
which does not exist and hence our __getattribute__
function will always throw AttributeError
.
Now, after __getattribute__
fails, __getattr__
is being tried for it ... well, instead of just resolving __dict__
to some value, it tries to get it as an attribute - and ends up calling__getattribute__
again. Hence we get.
....
__getattribute__: __dict__
__getattr__: __dict__
__getattribute__: __dict__
__getattr__: __dict__
__getattribute__: __dict__
__getattr__: __dict__
__getattribute__: __dict__
__getattr__: __dict__
__getattribute__: __dict__
__getattr__: __dict__
Traceback (most recent call last):
File "getattribute.py", line 15, in <module>
print(obj1.current)
File "getattribute.py", line 4, in __getattr__
d = self.__dict__
File "getattribute.py", line 4, in __getattr__
d = self.__dict__
File "getattribute.py", line 4, in __getattr__
d = self.__dict__
[Previous line repeated 328 more times]
File "getattribute.py", line 8, in __getattribute__
print('__getattribute__: ', item)
RecursionError: maximum recursion depth exceeded while calling a Python object
Had you used setattr(self, item, 0)
instead of looking up self.__dict__
this could have been "avoided":
class Count(object):
def __getattr__(self, item):
setattr(self, item, 0)
return 0
def __getattribute__(self, item):
if item.startswith('cur'):
raise AttributeError
return super(object, self).__getattribute__(item)
obj1 = Count()
print(obj1.current)
of course such code would not have been correct - trying to access any other attribute would have failed nevertheless.
Upvotes: 3