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

时间:2018-07-10 16:57:37

标签: python python-3.x getattr 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__却不需要?

1 个答案:

答案 0 :(得分:6)

  

我假设getattr()__getattr__的语法糖,但是它们的行为有所不同。

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

属性查找首先调用一个 different 钩子,即__getattribute__ method,该钩子默认情况下通过实例dict和类层次结构执行熟悉的搜索。 __getattr__仅在__getattribute__找不到属性时才会被调用。来自__getattr__ documentation

  

当默认属性访问失败并带有AttributeError时调用(__getattribute__()会引发AttributeError,因为 name 不是实例属性,也不是self的类树;或 name 属性的__get__()产生AttributeError)。

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

此外,诸如getattr()len()之类的函数对于dunder方法也不是语法糖。他们几乎总是做更多的工作,使用dunder方法将 hook 进行调用。有时涉及多个钩子,例如在这里,或者在通过调用类创建类的实例时。有时连接是相当直接的,例如在len()中,但是即使在简单的情况下,也要进行额外的检查,以确保钩子本身不负责。

  

为什么__setattr__需要呼叫super(),而__getattr__却不需要?

__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)