这里需要先说一下描述符的概念。
描述符:描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议。
描述符分数据描述符,只有__get__的描述符是非数据描述符,有__get__和__set__的描述符是数据描述符。
__get__(self, instance, owner)—获取属性时调用,返回设置的属性值,通常是_set_中的value,或者附加的其他组合值。
__set__(self, instance, value) — 设置属性时调用,返回None.
__delete__(self, instance)— 删除属性时调用,返回None
其中,instance是这个描述符属性所在的类的实例,而owner是描述符所在的类。
看下面例子
class A(object):
name = "unchange"
def __init__(self, value):
print "into A __init__"
self.value = value
def __get__(self, instance, owner):
print "into __get__"
print instance,owner
class B(object):
value = A(10)
def __init__(self, value):
print "into B __init__"
b = B(20)
# into A __init__
# into B __init__
print b.value
# into __get__
# <__main__.B object at 0x0000000001E13710>
# None
也就是,instance和owner就是类B的东西。
上一篇,我们讲到,当没有数据描述符是,实例访问属性的顺序是这样的:
当访问实例属性a.name时,其访问顺讯是:(假设没有数据描述符)
先进入__getattribute__
1、实例属性a.__dict__ 2、类属性A.__dict__ 3、父类及基类属性A.__bases__.__dict
搜索不到再进入__getattr__
当有数据描述符,或非数据描述符时,访问顺序变为:
先进入__getattribute__
1、如果该属性是数据描述符,进入__get__ 2、实例属性 3、非数据描述符
搜索不到再进入__getattr__
class A(object):
def __init__(self, name):
print "into A __init__"
self.name = name
def __get__(self, instance, owner):
print "into __get__"
def __set__(self, instance, value):
print "into __set__"
class B(object):
name = A("Tom") #假如没有这个数据描述符,输出结果就是“Bob”
def __init__(self, name, age):
print "into B __init__"
self.age = age
self.name = name
def __getattribute__(self, item):
print "into __getattribute__"
return object.__getattribute__(self, item)
b = B("Bob", 100)
# into A __init__
# into B __init__
# into __set__
print b.name
# into __getattribute__
# into __get__
# None
有数据描述符,依然会进入__getattribute__,然后进入__get__。若没有数据描述符,直接访问实例属性。
原因:在初始化__init__开始前,就开始对数据描述符name,即A(“Tom”)进行实例化。
当进入B的实例化的时候,已经对self.name进行了赋值,因为描述符的赋值操作会进入__set__(这里实际上__set__只做了打印操作,没有帮instance赋值)。
让实例b的__dict__里没有name这个属性,当然没办法在实例b得到想要的属性。
让A中的__set__方法不写时,name就是非数据描述符。
例如
class A(object):
def __init__(self, name):
print "into A __init__"
self.name = name
def __get__(self, instance, owner):
print "into __get__"
class B(object):
name = A("Tom") #假如没有这个数据描述符,输出结果就是“Bob”
def __init__(self, name, age):
print "into B __init__"
self.age = age
self.name = name
def __getattribute__(self, item):
print "into __getattribute__"
return object.__getattribute__(self, item)
b = B("Bob", 100)
# into A __init__
# into B __init__
# into __set__
print b.name
# into __getattribute__
# Bob
非数据描述符的优先级低于实例属性。
相关用法:1、属性访问、修改控制
class A(object):
def __init__(self, name):
print "into A __init__"
self.name = name
def __get__(self, instance, owner):
print "into __get__"
return instance.__dict__[self.name]
def __set__(self, instance, value):
print "into __set__"
if value < 0:
raise ValueError
instance.__dict__[self.name] = value
class B(object):
name = A("name")
age = A("age")
def __init__(self, name, age):
print "into B __init__"
self.age = age
self.name = name
def __getattribute__(self, item):
print "into __getattribute__"
return object.__getattribute__(self, item)
b = B("Bob", 20)
b.age = -1
print b.age
用__set__拦截赋值操作,不合理的值会抛错。__delete__的用法与__set__类似,在删除的时候拦截删除操作。
数据描述符跟@property一样具有属性访问控制的效果,但数据描述符能用于更多的属性控制,且不显代码累赘。
需要注意的地方:
1、重写__get__,__set__,__delete__避免进入死循环。
2、描述符被__getattribute()方法调用,重载__getattribute__()不当会阻止描述符自动调用。
3、数据描述符会重载实例字典,非数据描述符可能会被实例字典重载(属性访问顺序的原因)。