如果在乙個類中定義了 __get__() , __set__(), __delete__() 這三種方法之一,那麼這個類是乙個描述符。如果這種類只定義了get方法,那麼就是乙個非資料描述符,反之則是資料描述符。
描述符的用處就是,當乙個物件的某個屬性是乙個描述符時,你訪問這個描述符型別的屬性,可能會呼叫這個描述符的方法。譬如你獲取描述符的值時,可能會呼叫它的__get__()
.
我們先看一下這三個方法的docstring
:
def __delete__(self, *args, **kwargs): # real signature unknown
""" delete an attribute of instance. """
# 刪除乙個例項的屬性
def __set__(self, *args, **kwargs): # real signature unknown
""" set an attribute of instance to value. """
# 給例項的屬性設定乙個值
def __get__(self, *args, **kwargs): # real signature unknown
""" return an attribute of instance, which is of type owner. """
# 返回例項的屬性,該屬性屬於例項的型別所有者?(個人猜測可能是說,這個屬性要屬於這個例項的某個基類)
例項:
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__)
可以看出,例項b
的屬性中,只有乙個val
;類b
的屬性中,有乙個a
,且a
是類a
的例項化物件。
接下來,我們呼叫一下例項a
:
b = b()
b.aprint('**********')
b.a
get: self,instance,owner <__main__.a object at 0x03458e68> <__main__.b object at 0x03458f28> **********
get: self,instance,owner <__main__.a object at 0x03458e68> none
我們看一下什麼意思:
當呼叫b.a
時,程式會自動去呼叫b.__getattribute__('a')
, 也就是b.__dict__['a']
, 即通過物件 b 的字典去查詢屬性,但是在第一步我們已經知道, 物件 b 只有乙個屬性 ,既然在例項b中找不到a
。 所以會去父類中找,呼叫:type(b).__dict__['a'].__get__(b,type(b))
,也就是:b.__dict__['a'].__get__(b,b)
,列印了第一行的資訊,並返回了none
當呼叫b.a
時,會直接呼叫b.__dict__['a'].__get__(none,b)
,所以中間有個 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
可以看出,當呼叫了b.a=11
時,呼叫了描述符的__set__()
, 但是物件b
的例項屬性並沒有改變,依然只有val=20
, 同時類b的類屬性也沒有改變。 但是當呼叫b.a = 12
時,類屬性a
變成了12,並沒有呼叫描述符的__set__()
方法。
所以,結合上面的 docstring,我們可以看出,資料描述符應該是給例項使用的,類使用它用處不大,至少沒法呼叫它的__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> 11
當類a是乙個非資料描述符,也就是說將類a 中的 set 方法刪掉或者注釋掉,那麼例項的字典優先順序高,所以會使用 例項字典中的資料,即結果:
11
屬性查詢優先順序:
obj.__getattribute__()
資料描述符
例項的字典
類的字典(4,5排序並不準確,當兩者同名且同為類屬性時,後宣告賦的值,會覆蓋前面的賦值,譬如a=4;a=5;執行完成是5,因為程式是從上往下按順序執行的)
非資料描述符
父類的字典
__getattr__
補充乙個順序的**:感興趣的可以按順序注釋掉**,執行試試
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)
說了一堆有的沒的,其實描述符就是乙個特殊的實現,當你的乙個物件的屬性是描述符時,設定/賦值/讀取 這個屬性,都會觸發這個描述符內部相應實現的方法。從而可以實現一些定製化的內容。 Python物件屬性的查詢順序
1 查詢順序 1 類和父類字典的資料描述器 2 例項字典 3 類和父類字典中的非資料描述器 無論類有多少個繼承級別,該類物件的例項字典總是儲存了所有的例項變數,這也是 super 的意義之一。2 例項 def get attribute obj,name class definition obj.c...
順序查詢(python)
根據python中列表查詢某乙個數 alist 1,2,3,4,5,6,3,8,9 sign false 初始值為沒找到 x int input 請輸入要查詢的整數 for i in range len alist if alist i x print 整數 d在列表中,在第 d個數 x,i 1 s...
python屬性查詢
python中執行obj.attr時,將呼叫特殊方法obj.getattribute attr 該方法執行搜尋來查詢該屬性,通常涉及檢查特性 查詢例項字典 查詢類字典以及搜尋基類。如果搜尋過程失敗,最終會嘗試呼叫類的 getattr 方法。如果這也失敗,則丟擲attributeerror異常。具體步...