I assume getattr()
is a syntactic sugar for __getattr__
, but they behave differently.
That's because the assumption is incorrect. getattr()
goes through the entire attribute lookup process, of which __getattr__
is only a part.
Attribute lookup first invokes a different hook, namely the __getattribute__
method, which by default performs the familiar search through the instance dict and class hierarchy. __getattr__
will be called only if the attribute hasn't been found by __getattribute__
. From the __getattr__
documentation:
Called when the default attribute access fails with an AttributeError
(either __getattribute__()
raises an AttributeError
because name is not an instance attribute or an attribute in the class tree for self
; or __get__()
of a name property raises AttributeError
).
In other words, __getattr__
is an extra hook to access attributes that don't exist, and would otherwise raise AttributeError
.
Also, functions like getattr()
or len()
are not syntactic sugar for a dunder method. They almost always do more work, with the dunder method a hook for that process to call. Sometimes there are multiple hooks involved, such as here, or when creating an instance of a class by calling the class. Sometimes the connection is fairly direct, such as in len()
, but even in the simple cases there are additional checks being made that the hook itself is not responsible for.
Why does __setattr__
need to call super()
, while __getattr__
doesn't?
__getattr__
is an optional hook. There is no default implementation, which is why super().__getattr__()
doesn't work. __setattr__
is not optional, so object
provides you with a default implementation.
Note that by using getattr()
you created an infinite loop! instance.non_existing
will call __getattribute__('non_existing')
and then __getattr__('non_existing')
, at which point you use getattr(..., 'non_existing')
which calls __getattribute__()
and then __getattr__
, etc.
In this case, you should override __getattribute__
instead:
class A(object):
x = 10
def __getattribute__(self, attribute):
return super().__getattribute__(attribute.lower())
def __setattr__(self, attribute, value):
return super().__setattr__(attribute.lower(), value)