__getattribute__ 与 __getattr__

getattribute

属性拦截器,所有对象尝试去访问属性的时候,就会调用该方法

class A:
    x = '类属性x'
    def __init__(self,y):
        self.y = y

    def __getattribute__(self,attr_name):
        print("进入到__getattribute__方法")
        return super().__getattribute__(attr_name)


a = A('实例属性y')

print(a.y)
# 输出: 进入到__getattribute__方法
# 实例属性y

print(a.x)
#输出: 进入到__getattribute__方法
#类属性x


print(A.x)
#输出: 类属性x

当对象去访问实例属性、类属性时,都会进入到该实例所在的类【这就是为什么A.x没有输出进入到__getattribute__方法的原因 type(A)是type】的
__getattribute__(self,attr_name)方法中

getattr

当尝试获取一个不存在的属性时发生的行为

举例:

class Test:
    name = "测试"

    def __getattr__(self,attr_name):
        if attr_name == "height":
            return "不晓得好高"
        else:
            raise AttributeError

test = Test()
print(test.name) #输出: 测试
print(test.height)#输出: 不晓得好高
print(test.gendler)# 报错
print(Test.height)# 报错, 类Test 所属的类type中没有设置对应height的__getattr__方法

当对象去访问一个该对象实例属性、所属类属性、所属类父类属性都不存在的属性时候,就会进入到该对象所属类的__getattr__(self,attr_name)方法中【这也就是为什么Test.height会报错的原因,因为类Test所属类是type,没有设置这个__getattr__】

访问顺序

对象去访问属性的时候,可能存在实例属性、类属性、父类属性、__getattr__方法设置的属性获取 同名的情况,优先级如下:

class C:
    x = 'C_X'

class D(C):
    x = 'D_X'

    def __getattr__(self,attr_name):
        if attr_name == "x":
            return 999
        else:
            raise  AttributeError


d = D()

# 优先级1:实例属性
d.__dict__['x'] = 6
print(d.x)    #  输出的是6

# 优先级2:类属性
del d.x
print(d.x)    #  输出的是类属性 D_X

# 优先级3:父类属性
del D.x
print(d.x)    # 输出的是父类属性 C_X

# 优先级4:__getattr__
del C.x
print(d.x)    # 输出的是999

优先级如下:

  • 实例属性
  • 类属性
  • 父类属性(mro顺序)
  • __getattr__

每一次去获取属性,都会先进到__getattribute__ 方法,然后根据上述顺序,如果类属性是描述符:数据描述符优先级>同名实例属性, 实例属性>同名非数据描述符

拓展:

__getattribute__只有在访问了才会去调用

给对象的属性赋值的时候,并不会调用,如果需要获取到属性的值,就会调用

class A:
    def __init__(self,x):
        self.x =x 
    def __getattribute__(self,attr_name):
        print("进入到__getattribute__方法")
        return super().__getattribute__(attr_name)


a = A({"name":"kobe"})

a.x = {"height":"198"}  # 直接赋值,不会调用__getattribute__

a.x['height'] = '200' # 这里相当于要先获取到a.x,然后再去修改a.x的'height'的值,所以触发了访问属性,会调用__getattribute__

访问属性时,并不是直接去调用__getattribute__方法

其实在点操作符去访问属性的时候,是通过了一个hook函数来执行查找

def getattr_hook(obj, name):
    "Emulate slot_tp_getattr_hook() in Objects/typeobject.c"
    try:
		#尝试 执行__getattribute__方法
        return obj.__getattribute__(name)
    except AttributeError:
		# 如果 该对象所属的类中没有 __getattr__方法,直接报错没有找到该属性
        if not hasattr(type(obj), '__getattr__'):
            raise
	# 如果该对象所属类有__getattr__方法,就去调用该方法
    return type(obj).__getattr__(obj, name)             # __getattr__

根据上述原理,如果用户直接调用 obj.__getattribute__(),__getattr__() 的补充查找机制就会被绕过。
测试如下:

class Test:
    def __getattr__(self,attr_name):
        return "这是__getattr__方法"

test = Test()
print(test.x)  # 访问一个不存在的属性 会正常走到__getattr__方法去

print(test.__getattribute__('x'))  # 报错没有该属性

上述源码也解释了,实例在访问不存在的属性的时候,调用getattr方法,就会进入到该对象所属类的__getattr__(self,attr_name)方法中 这就表示,直接实例test所属的类Test去访问不存在的属性的时候是走不到这个方法里的,同理,如果给实例test.__dict__添加一个getattr方法,但是test所属的Test类是没有getattr方法的,这时候test去访问不存在的属性 也会报错

class Test:
    pass
test = Test()
test.__getattr__ = lambda attr_name:"固定返回内容"
print(test.xx) #报错没有该属性
posted @ 2022-01-06 17:25  Alantammm  阅读(106)  评论(0编辑  收藏  举报