這裏需要先說一下描述符的概念。
描述符:描述符本質就是一個新式類,在這個新式類中,至少實現了__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