数据描述符,属性查找优先级

如果在一个类中定义了 __get__() , __set__(), __delete__() 这三种方法之一,那么这个类是一个描述符。如果这种类只定义了get方法,那么就是一个非数据描述符,反之则是数据描述符。

实例:

class A(object):
def __init__(self):
self.value = None
def __set__(self, instance, value): # self:类A的实例,也是类B的属性a;instance:类B的实例b;value:通过b.a的赋值
print('set: self,instance,value',self,instance,value)
self.value = value
return self.value
def __get__(self, instance, owner):# instance:类B的实例b;owner:类B
print('get: self,instance,owner',self,instance,owner)
return self.value
class B(object):
a = A()
def __init__(self):
self.val = 20

上述代码中,有两个类,A,B。先看类 B,有一个类属性 a , 且 a 是类 A 的实例,我们先来实例化一下类 B ,看一下 类B 和实例 b 的属性:

b = B()
print(b.__dict__)
print(B.__dict__)
{'val': 20}
{'__module__': '__main__', 'a': <__main__.a object at>, '__init__': , '__dict__': , '__weakref__': , '__doc__': None}

可以看出,实例 b 的属性中,只有一个 val ;类 B 的属性中,有一个 a ,且 a 是类 A 的实例化对象。

接下来,我们调用一下 a:

b = B()
print(b.a,B.a)
get: self,instance,owner <__main__.a object at> <__main__.b object at> 
get: self,instance,owner <__main__.a object at> None 
None None

结果竟然打出来三行话,我们看一下什么意思:

第一行,是通过 b.a 调用产出的。当调用 b.a 时,程序会自动去调用 b.__getattribute__('a') , 也就是 b.__dict__['a'], 即通过对象 b 的字典去查找属性,但是在第一步我们已经知道, 对象 b 只有一个属性 {'val': 20} ,既然在实例b中找不到‘a'。 因为类A 是一个数据描述符,并且 a 是B的类属性,所以会去调用:B.__dict__['a'].__get__(b,B),得到第一行打印数据

第二行,是通过 B.a 调用产出的。当调用 B.a 时,会直接调用 B.__dict__['a'].__get__(None,B),所以中间有个 None

第三行,很简单了,前两次的 return 都是None,就打印了None

现在,我们尝试给 b.a 赋值

b = B()
b.a = 11
print(b.__dict__)
print(B.__dict__)
B.a = 12
print(b.__dict__)
print(B.__dict__)
set: self,instance,value <__main__.a object at> <__main__.b object at> 11
{'val': 20}
{'__module__': '__main__', 'a': <__main__.a object at>, '__init__': , '__dict__': , '__weakref__': , '__doc__': None}
{'val': 20}
{'__module__': '__main__', 'a': 12, '__init__': , '__dict__': , '__weakref__': , '__doc__': None}

可以看出,当调用了 b.a=11 时,调用了 描述符 的__set__() , 但是对象b 的实例属性并没有改变,依然只有 val=20, 同时类B的类属性也没有改变。 但是当调用 B.a = 12 时,类属性 a变成了12,并没有调用 描述符的 __set__() 方法。

如果类属性的描述符对象和实例对象的属性同名,如果查找?

也就是说,如果把类B改成:

class B(object):
a = A()
def __init__(self):
self.val = 20
self.a = 11

此时调用 b.a ,会如何?

当类A是一个数据描述符,也就是说 类A包含 set 方法,此时数据描述符优先级高,所以结果:

set: self,instance,value <__main__.a object at> <__main__.b object at> 11
get: self,instance,owner <__main__.a object at> <__main__.b object at> 
11

当类A是一个非数据描述符,也就是说将类A 中的 set 方法删掉或者注释掉,那么实例的字典优先级高,所以会使用 实例字典中的数据,即结果:

11

属性查询优先级:

obj.__getattribute__()

数据描述符

实例的字典

类的字典(4,5排序并不准确,当两者同名且同为类属性时,后声明赋的值,会覆盖前面的赋值,譬如a=4;a=5;执行完成是5,因为程序是从上往下按顺序执行的)

非数据描述符

父类的字典

__getattr__

补充一个顺序的代码:感兴趣的可以按顺序注释掉代码,运行试试

class Quantity1(object):
def __get__(self, instance, owner):
return 2
def __set__(self, instance, val):
pass
class Quantity2(object):
def __get__(self, instance, owner):
return 5
class A(object):
val = 6 # 6 父类属性
x = None
class B(A):
val = Quantity2() # 5 非覆盖型描述符
val = 4 # 4 类属性,4和5排序并不准确,当两者都为类属性时,后声明的变量会覆盖之前的赋值,因为程序是从上上下执行的。
val = Quantity1() # 2 覆盖型描述符
def __init__(self):
super(B, self).__init__()
self.val = 3
def __getattr__(self, name): # 7 __getattr__
return 7
def __getattribute__(self, name): # 1 __getattribute__
return 1
b = B()
print(b.val)