python 屬性查詢順序,資料描述符

2022-06-20 19:12:15 字數 4816 閱讀 4515

如果在乙個類中定義了 __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

上述**中,有兩個類,ab。先看類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異常。具體步...