覆盖 getattr 和 setattr 的方式的根本区别

the fundamental differences of the way to overwrite getattr and setattr

我希望使属性不区分大小写。但是覆盖 __getattr____setattr__ 有点不同,如以下玩具示例所示:

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

我有两点很困惑

  1. 我假设 getattr()__getattr__ 的语法糖,但它们的行为不同。
  2. 为什么 __setattr__ 需要调用 super(),而 __getattr__ 不需要?

I assume getattr() is a syntactic sugar for __getattr__, but they behave differently.

那是因为假设不正确getattr()遍历了整个属性查找过程,__getattr__只是其中的一部分。

属性查找首先调用一个 different 挂钩,即 __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).

换句话说,__getattr__ 是一个 extra 挂钩,用于访问不存在的属性,否则会引发 AttributeError.

此外,getattr()len() 等函数不是 dunder 方法的语法糖。他们几乎总是做更多的工作,使用 dunder 方法 hook 供该进程调用。有时会涉及多个挂钩,例如此处,或者通过调用 class 创建 class 的实例时。有时连接是相当直接的,例如在 len() 中,但即使在简单的情况下,也会进行额外的检查,钩子本身不负责。

Why does __setattr__ need to call super(), while __getattr__ doesn't?

__getattr__ 是一个 可选的 挂钩。没有默认实现,这就是 super().__getattr__() 不起作用的原因。 __setattr__ 不是 可选的,因此 object 为您提供默认实现。

请注意,使用 getattr() 您创建了一个无限循环! instance.non_existing 将调用 __getattribute__('non_existing') 然后 __getattr__('non_existing'),此时你使用 getattr(..., 'non_existing') 调用 __getattribute__() 然后 __getattr__,等等

在这种情况下,您应该改写 __getattribute__

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)