My hope is to make attributes case-insensitive. But overwriting __getattr__
and __setattr__
are somewhat different, as indicated by the following toy example:
class A(object):
x = 10
def __getattr__(self, attribute):
return getattr(self, attribute.lower())
## following alternatives don't work ##
# def __getattr__(self, attribute):
# return self.__getattr__(attribute.lower())
# def __getattr__(self, attribute):
# return super().__getattr__(attribute.lower())
def __setattr__(self, attribute, value):
return super().__setattr__(attribute.lower(), value)
## following alternative doesn't work ##
# def __setattr__(self, attribute, value):
# return setattr(self, attribute.lower(), value)
a = A()
print(a.x) ## output is 10
a.X = 2
print(a.X) ## output is 2
I am confused by two points.
getattr()
is a syntactic sugar for __getattr__
, but they behave differently.__setattr__
need to call super()
, while __getattr__
doesn't?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 anAttributeError
because name is not an instance attribute or an attribute in the class tree forself
; or__get__()
of a name property raisesAttributeError
).
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 callsuper()
, 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)