__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) #报错没有该属性