2
class test():
  name = 'arthur'

  def __getattribute__(self, value):
    return super().__getattribute__(value)

x = test()
x.name

------------------------------------------
Output: 'arthur'

I'm trying to understand the underlying mechanism of __getattribute__. I understand (hopefully) that super().__getattribute__(value) reaches out to the object class in this case. But how actually does object fetch the name value out of the test() class? And how could one fetch an attribute value with __getattribute__ if coded in a class by oneself while avoiding recursion?

As I said, I want to understand the underlying mechanisms, I know this isn't the way how you would usually handle things.

Thanks!

Robey
  • 53
  • 1
  • 8
  • https://stackoverflow.com/questions/3278077/difference-between-getattr-vs-getattribute – mrkwjc Dec 13 '19 at 20:29
  • I read that thread before, none of the answers answer my specific question, but thanks anyway – Robey Dec 13 '19 at 20:35

2 Answers2

2

You can’t code this in Python. It’s a C function that Python calls:

PyObject *
PyObject_GetAttr(PyObject *v, PyObject *name)
{
    PyTypeObject *tp = Py_TYPE(v);

    if (!PyUnicode_Check(name)) {
        PyErr_Format(PyExc_TypeError,
                     "attribute name must be string, not '%.200s'",
                     name->ob_type->tp_name);
        return NULL;
    }
    if (tp->tp_getattro != NULL)
        return (*tp->tp_getattro)(v, name);
    if (tp->tp_getattr != NULL) {
        const char *name_str = PyUnicode_AsUTF8(name);
        if (name_str == NULL)
            return NULL;
        return (*tp->tp_getattr)(v, (char *)name_str);
    }
    PyErr_Format(PyExc_AttributeError,
                 "'%.50s' object has no attribute '%U'",
                 tp->tp_name, name);
    return NULL;
}

https://github.com/python/cpython/blob/8289e27393395ee903bd096d42e07c112d7f15c6/Objects/object.c#L1001-L1024

This is object.__getattribute__. It’s called in your example because all classes inherit from object.

Anonymous
  • 11,748
  • 6
  • 35
  • 57
  • So just to make sure that I understand it correctly, when ```super()__getattribute__(value)``` reaches object, object calls an internal C-function named ```builtin_getatrr``` which searches for the desired value via inheritance search while somehow avoiding to trigger ```__getattribute__``` again? ( I don't understand the C code that's why I ask) – Robey Dec 13 '19 at 20:33
  • @Arthred Updated. The last code was for `getattr` which was maybe why it was confusing. – Anonymous Dec 13 '19 at 20:46
  • Thank you so much! Do I need to understand how ```object.__getattribute__``` avoids ending up in a recursive loop and actually manages to fetch the desired value? – Robey Dec 13 '19 at 20:51
  • Because until now everywhere I've read we need to call ```object.__getattribute``` because otherwise we end up in a loop and I would love to know how object actually avoids that – Robey Dec 13 '19 at 20:52
  • @Arthred You’re welcome. You’re using `super()` which is `object` not `test`. There is no recursion. – Anonymous Dec 13 '19 at 20:53
  • Maybe my question is just really poorly articulated or my question is just dumb. But I was wondering, how does ```object.__getattribute__``` return the attribute? Because when it calls let's say ```x.name``` it would invoke ```test.__getattribute``` again which in turn would call ```object.__getattribute__``` again. Obviously this doesn't happen, but how does ```object.__getattribute__``` access the ```name``` attribute of ```test``` without calling the method again then? @Anonymous – Robey Dec 13 '19 at 21:04
  • @Arthred I think I see where you’re confused. Python doesn’t need to call `__getattr__`. It knows all the attributes on an object. That’s only called when you get an attribute from Python code. – Anonymous Dec 13 '19 at 21:24
2

First of all, the __getattribute__ is a magic method (aka dunder method/double underscore method). This is a property/attribute accessor method and it intercepts on every property/attribute access even if the property/attribute is available in the class itself. On the other hand, the __getattr__ magic method is called only if the property you are accessing doesn't exist in the class/instance. Anyways...

There is also one more important thing to notice that, as you might already know that, all classes extends/inherits the base object class implicitly or explicitly. So, any class you define, the default parent class is the built-in object class. You are confused as you asked:

I understand (hopefully) that super().__getattribute__(value) reaches out to the object class in this case. But how actually does object fetch the name value out of the test() class?

So lets see some basic examples first:

class Foo:
    def __init__(self, name):
        self.name = name

    def __getattribute__(self, name):
        return super().__getattribute__(name)

is equivalent to

class Foo(object):
    def __init__(self, name):
        self.name = name

    def __getattribute__(self, name):
        return object.__getattribute__(self, name)

So, when you call

object.__getattribute__(self, name)

You are passing the context (an instance of the class) explicitly to the parent (object) class. so the parent/object class knows the context and get the attribute from that passed instance. On the other hand, when you call:

super().__getattribute__(name)

Python sets the context for you. So, you can imagine something like this:

super(test, self).__getattribute__(name) # it's valid

But in this case, it's implicit, that's how the super() call knows where to lookup for the attribute. It's worth mentioning that, Python's super() builtin function returns a proxy object, a substitute object that can call the method of the base class via delegation and also super() can take two parameters (as you've seen in previous code snippet), the first is the subclass type (in this case test), and the second parameter is an object, an instance of that subclass (test).

In Python 3, the super(test, self) call is equivalent to the parameterless super() call. Hope I've clarified your confusions. You may read more about super().

The Alpha
  • 143,660
  • 29
  • 287
  • 307