1

I am trying to wrap my head around how __getattribute__ and __getattr__ work in Python with respect to uninstantiated classes and subclassing. Specifically I want to capture a getattribute call in the super class and potentially modify it accordingly.

In the following code snippet, I would have expected at least some intermediate print statements when any of the methods are called, but I am not getting anything. The class variable is found and returned but seemingly none of the magic methods are called. Why is that?

class SuperbClass:
    def __getattribute__(self, attr):
        print(f"called in superb, found {attr}")
        # get the attribute via `object`
        return f"{super(SuperbClass, self).__getattribute__(attr)}MODIFIED_IN_SUPER"

    def __getattr__(self, attr):
        self.__getattribute__(attr)

class SubberClass(SuperbClass):
    MyClassVar = "I am a cool message"

    def __getattribute__(self, attr):
        print("called in subber")
        super(SuperbClass, self).__getattribute__(attr)

    def __getattr__(self, attr):
        self.__getattribute__(attr)


if __name__ == '__main__':
    print(SubberClass.MyClassVar)
    # Only prints `I am a cool message`
    inst = SubberClass()
    print(inst.MyClassVar)
    # prints called in subber
    # and prints None

EDIT: updated code to reflect the difference between class and instance

Bram Vanroy
  • 27,032
  • 24
  • 137
  • 239

1 Answers1

4

A class's __getattribute__ and __getattr__ handle attribute resolution for instances of the class. If you want to handle attribute resolution for the class itself, you need to define __getattribute__ or __getattr__ on the class's class: its metaclass.

class Meta(type):
    def __getattribute__(self, attr):
        print('*This* prints.')
        return super().__getattribute__(attr)

class Blah(metaclass=Meta):
    MyClassVar = 'blah'

if __name__ == '__main__':
    Blah.MyClassVar
user2357112
  • 260,549
  • 28
  • 431
  • 505
  • It's probably useful to mention that `__getattr__` would not be called even when using a metaclass that defines the method because `MyClassVar` can be found in one of the "usual ways", by looking at `Blah.__dict__` in this specific case. – timgeb Nov 02 '21 at 10:23
  • Interesting! Can you provide more information about what `type` does in this context? Alternatively, external resource to read up on are welcome too. – Bram Vanroy Nov 02 '21 at 10:30
  • 1
    `type` isn't simply a function that gives you the type of an object (although it does that) - it's also *the metaclass of `object`*. Through deep magic, calling `type` can tell you the type of the passed-in object, or it can construct a new type (i.e., class that has `type` as its metaclass, which is what you normally get when using a `class` block). Metaclasses are also classes; here, we create a metaclass by inheriting from the base `type`, and then use it as a metaclass for `Blah`. – Karl Knechtel Nov 02 '21 at 10:44