Olga Zhukova
Olga Zhukova

Reputation: 389

Get an information about defined attributes without __dict__ or dir()

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

Answers (2)

Martijn Pieters
Martijn Pieters

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

user2357112
user2357112

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

Related Questions