I read a bit on Python's object attribute lookup:
Seems pretty straight forward, so I tried it out (python3):
class A:
def __getattr__(self, attr):
return (1,2,3)
a = A()
a.foobar #returns (1,2,3) as expected
a.__getattribute__('foobar') # raises AttributeError
My question is, aren't the two supposed to be identical?
Why does the second one raise an attribute error?
So apparently the answer is that the logic for a.foobar
IS different from the logic for a.__getattribute("foobar")
. According to the data model: a.foobar
calls a.__getattribute("foobar")
and if it raises an AttributeError, it calls a.-__getattr__('foobar')
So it seems the article has a mistake in their diagram. Is this correct?
And another question: Where does the real logic for a.foobar
sit? I thought it was in __getattribute__
but apparently not entirely.
Edit:
Not a duplicate of Difference between __getattr__ and __getattribute__.
I am asking here what is the different between object.foo
and object.__getattribute__("foo")
.
This is different from __getattr__
vs __getatribute__
which is trivial...
It's easy to get the impression that __getattribute__
is responsible for more than it really is. thing.attr
doesn't directly translate to thing.__getattribute__('attr')
, and __getattribute__
is not responsible for calling __getattr__
.
The fallback to __getattr__
happens in the part of the attribute access machinery that lies outside __getattribute__
. The attribute lookup process works like this:
__getattribute__
method through a direct search of the object's type's MRO, bypassing the regular attribute lookup process.__getattribute__
.
__getattribute__
returned something, the attribute lookup process is complete, and that's the attribute value.__getattribute__
raised a non-AttributeError, the attribute lookup process is complete, and the exception propagates out of the lookup.__getattribute__
raised an AttributeError. The lookup continues.__getattr__
method the same way we found __getattribute__
.
__getattr__
, the attribute lookup process is complete, and the AttributeError from __getattribute__
propagates.__getattr__
, and return or raise whatever __getattr__
returns or raises.At least, in terms of the language semantics, it works like that. In terms of the low-level implementation, some of these steps may be optimized out in cases where they're unnecessary, and there are C hooks like tp_getattro
that I haven't described. You don't need to worry about that kind of thing unless you want to dive into the CPython interpreter source code.