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

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