Python 魔法方法(五) 从__get__,__set__, __delete__再探属性访问顺序

这里需要先说一下描述符的概念。

描述符:描述符本质就是一个新式类,在这个新式类中,至少实现了__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> <class '__main__.B'>
# 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、数据描述符会重载实例字典,非数据描述符可能会被实例字典重载(属性访问顺序的原因)。
————————————————
版权声明:本文为CSDN博主「yusuiyu」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/yusuiyu/article/details/87971091

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章