数据描述符,属性查找优先级
如果在一个类中定义了 __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 0x0163FD70>, '__init__': <function B.__init__ at 0x07845078>, '__dict__': <attribute '__dict__' of 'B' objects>, '__weakref__': <attribute '__weakref__' of 'B' objects>, '__doc__': None}
可以看出,实例 b 的属性中,只有一个 val ;类 B 的属性中,有一个 a ,且 a 是类 A 的实例化对象。
-
接下来,我们调用一下 a:
b = B() print(b.a,B.a)
get: self,instance,owner <__main__.A object at 0x011CFD70> <__main__.B object at 0x011CFDD0> <class '__main__.B'> get: self,instance,owner <__main__.A object at 0x011CFD70> None <class '__main__.B'> None None
结果竟然打出来三行话,我们看一下什么意思:
第一行,是通过 b.a 调用产出的。当调用 b.a 时,程序会自动去调用 b.__getattribute__('a') , 也就是 b.__dict__['a'], 即通过对象 b 的字典去查找属性,但是在第一步我们已经知道, 对象 b 只有一个属性 {'val': 20} ,既然在实例b中找不到‘a'。 因为类A 是一个数据描述符,并且 a 是B的类属性,所以会去调用:type(b).__dict__['a'].__get__(b,type(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 0x037CFD70> <__main__.B object at 0x037CFDD0> 11 {'val': 20} {'__module__': '__main__', 'a': <__main__.A object at 0x037CFD70>, '__init__': <function B.__init__ at 0x07E85078>, '__dict__': <attribute '__dict__' of 'B' objects>, '__weakref__': <attribute '__weakref__' of 'B' objects>, '__doc__': None} {'val': 20} {'__module__': '__main__', 'a': 12, '__init__': <function B.__init__ at 0x07E85078>, '__dict__': <attribute '__dict__' of 'B' objects>, '__weakref__': <attribute '__weakref__' of 'B' objects>, '__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 0x009DFD70> <__main__.B object at 0x009DFDD0> 11 get: self,instance,owner <__main__.A object at 0x009DFD70> <__main__.B object at 0x009DFDD0> <class '__main__.B'> 11
-
当类A是一个非数据描述符,也就是说将类A 中的 set 方法删掉或者注释掉,那么实例的字典优先级高,所以会使用 实例字典中的数据,即结果:
11
-
属性查询优先级:
-
obj.__getattribute__()
-
数据描述符
-
实例的字典
-
类的字典(4,5排序并不准确,当两者同名且同为类属性时,后声明赋的值,会覆盖前面的赋值,譬如a=4;a=5;执行完成是5,因为程序是从上往下按顺序执行的)
-
非数据描述符
-
父类的字典
-
__getattr__
补充一个顺序的代码:感兴趣的可以按顺序注释掉代码,运行试试
原链接:https://www.cnblogs.com/wickedpriest/p/11984887.html
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)