I read a bit on python\'s object attribute lookup (here: https://blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses/#object-attribute-lookup).
Seems pretty stra
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.