Reputation: 389
So, let's say I want to write my own class, rewriting __getattribute__
function. Every time, when someone calls attribute, which was not defined, I want it to generate a random int.
X = GetAttribute()
print(X.predefined_attribute) # "First attribute"
X.attr2 = "Hi"
print(X.attr2) # "Hi"
print(X.attr3) # random int
Obviously, I can't write something like this, as it would cause a recursion.
class GetAttribute(object):
def __init__(self):
self.predefined_attribute = "First attribute"
def __getattribute__(self, attr):
if attr not in self.__dict__: # the bad line
return randint(0, 9999)
else:
return object.__getattribute__(self, attr)
How, without using __dict__
, can I get an information about defined attributes?
Upvotes: 1
Views: 72
Reputation: 1121196
I strongly urge you to reconsider overriding __getattribute__
and use the object.__getattr__()
hook instead. That method is called automatically for any missing attribute, and won't interfere with dir()
or __dict__
introspection:
class GetAttribute(object):
def __init__(self):
self.predefined_attribute = "First attribute"
def __getattr__(self, attr):
# self.__dict__ can be used here but is not needed for your
# sample usecase.
return randint(0, 9999)
Your own implementation is flawed because you failed to check the class for your attribute. __dict__
is a descriptor on the class, and trying to access self.__dict__
is handled by object.__getattribute__
too, triggering your infinite recursion. You can avoid the issue entirely by using object.__getattribute__
first. You could just catch the AttributeError
exception this could throw:
def __getattribute__(self, attr):
try:
return object.__getattribute__(self, attr)
except AttributeError:
return randint(0, 9999)
The more painful path would be to re-implement the descriptor protocol to retrieve your __dict__
attribute before testing:
def __getattribute__(self, attr):
cls = type(self)
# retrieve the __dict__ descriptor, and bind it to the instance
__dict__ = cls.__dict__['__dict__'].__get__(self)
# test against the instance dictionary and all classes in the MRO
if attr not in __dict__ and not any(attr in c.__dict__ for c in cls.__mro__):
return randint(0, 9999)
return object.__getattribute__(self, attr)
or you could access self.__dict__
via object.__getattribute__(self, '__dict__')
. You do have to test the class MRO too, because those provide attributes for your instances too; you wouldn't want X.__class__
to return a random integer rather than GetAttribute
itself.
However, this use-case is already covered by implementing __getattr__
instead, a much cleaner and simpler option.
Last but not least, instead of using object.__getattribute__(self, ...)
, you should use super().__getattribute__(...)
to ensure you are not skipping any other __getattribute__
hooks in your class hierarchy.
Upvotes: 2
Reputation: 280182
If you need to bypass your own __getattribute__
, for example to get at the "real" self.__dict__
, you can explicitly call the superclass __getattribute__
:
if attr not in super().__getattribute__('__dict__'):
However, for your case, it'd probably be easier to just implement __getattr__
instead of __getattribute__
. __getattr__
is only called for attribute lookups that __getattribute__
raises an AttributeError
on:
def __getattr__(self, name):
return randint(0, 9999)
Upvotes: 0