Python屬性描述符(二)

來源:互聯網
上載者:User

標籤:ring   不同   function   one   影響   force   shadow   forward   其他   

Python存取屬性的方式特別不對等,通過執行個體讀取屬性時,通常返回的是執行個體中定義的屬性,但如果執行個體未曾定義過該屬性,就會擷取類屬性,而為執行個體的屬性賦值時,通常會在執行個體中建立屬性,而不會影響到類本身。這種不對等的方式對描述符類也有影響。

def cls_name(obj_or_cls):  # 傳入一個執行個體,返回類名    cls = type(obj_or_cls)    if cls is type:        cls = obj_or_cls    return cls.__name__.split(‘.‘)[-1]def display(obj):    cls = type(obj)    if cls is type:  # 如果obj是一個類,則進入該分支        return ‘<class {}>‘.format(obj.__name__)    elif cls in [type(None), int]:  # 如果obj是None或者數值,進入該分支        return repr(obj)    else:  # 如果obj是一個執行個體        return ‘<{} object>‘.format(cls_name(obj))def print_args(name, *args):    pseudo_args = ‘, ‘.join(display(x) for x in args)    print(‘-> {}.__{}__({})‘.format(cls_name(args[0]), name, pseudo_args))class Overriding:  # <1>    """a.k.a. data descriptor or enforced descriptor"""    def __get__(self, instance, owner):        print_args(‘get‘, self, instance, owner)    def __set__(self, instance, value):        print_args(‘set‘, self, instance, value)class OverridingNoGet:  # <2>    """an overriding descriptor without ``__get__``"""    def __set__(self, instance, value):        print_args(‘set‘, self, instance, value)class NonOverriding:  # <3>    """a.k.a. non-data or shadowable descriptor"""    def __get__(self, instance, owner):        print_args(‘get‘, self, instance, owner)class Managed:  # <4>    over = Overriding()    over_no_get = OverridingNoGet()    non_over = NonOverriding()    def spam(self):  # <5>        print(‘-> Managed.spam({})‘.format(display(self)))

    

  1. 有__get__和__set__方法的典型覆蓋型描述符,在這個樣本中,各個方法都調用了print_args()函數
  2. 沒有__get__方法的覆蓋型描述符
  3. 沒有__set__方法,所以這是非覆蓋型描述符
  4. 託管類,使用各個描述符類的一個執行個體
  5. spam方法放這裡是為了對比,因為方法也是描述符

覆蓋型描述符

實現__set__方法的描述符屬於覆蓋型描述符,雖然描述符是類屬性,但是實現了__set__方法的話,會覆蓋對執行個體屬性的賦值操作。特性也是覆蓋型描述符,見Python動態屬性和特性(二),如果沒有提供設值函數,property類中的fset方法就會拋出AttributeError異常,指明那個屬性時唯讀

下面我們來看下覆蓋型描述符的行為:

>>> obj = Managed()  # <1>>>> obj.over  # <2>-> Overriding.__get__(<Overriding object>, <Managed object>, <class Managed>)>>> Managed.over  # <3>-> Overriding.__get__(<Overriding object>, None, <class Managed>)>>> obj.over = 8  # <4>-> Overriding.__set__(<Overriding object>, <Managed object>, 8)>>> obj.over  # <5>-> Overriding.__get__(<Overriding object>, <Managed object>, <class Managed>)>>> obj.__dict__["over"] = 9  # <6>>>> vars(obj)  # <7>{‘over‘: 9}>>> obj.over  # <8>-> Overriding.__get__(<Overriding object>, <Managed object>, <class Managed>)

  

  1. 建立受管理的執行個體Managed對象
  2. obj.over觸發描述符的__get__方法,__get__方法第一個參數是描述符執行個體,第二個參數的值是受管理的執行個體obj,第三個參數是託管類對象
  3. Managed.over 觸發描述符的__get__方法,第二個參數(instance)的值是 None,第一個和第三個同上
  4. 為obj.over賦值,觸發描述符的__set__方法,最後一個參數的值是8
  5. 讀取 obj.over,仍會觸發描述符的__get__方法
  6. 跳過描述符,直接通過obj.__dict__屬性設值
  7. 確認值在obj.__dict__屬性中,在over鍵名下
  8. 然而,即使是名為over的執行個體屬性,Managed.over描述符仍會覆蓋讀取 obj.over 這個操作

沒有__get__方法的覆蓋型描述符

>>> obj.over_no_get  # <1><descriptorkinds.OverridingNoGet object at 0x0000001742369780>>>> Managed.over_no_get  # <2><descriptorkinds.OverridingNoGet object at 0x0000001742369780>>>> obj.over_no_get = 8  # <3>-> OverridingNoGet.__set__(<OverridingNoGet object>, <Managed object>, 8)>>> obj.over_no_get  # <4><descriptorkinds.OverridingNoGet object at 0x0000001742369780>>>> obj.__dict__[‘over_no_get‘] = 6  # <5>>>> obj.over_no_get  # <6>6>>> obj.over_no_get = 7  # <7>-> OverridingNoGet.__set__(<OverridingNoGet object>, <Managed object>, 7)>>> obj.over_no_get  # <8>6

    

  1. 描述符執行個體沒有__get__方法,所以obj.over_no_get從類中擷取描述符執行個體
  2. 直接從託管類中讀取over_no_get屬性,也就是描述符執行個體
  3. 為obj.over_no_get賦值hui會觸發描述符類的__set__方法
  4. 因為__set__方法沒有修改屬性,所以在此讀取obj.over_no_get擷取的仍然是託管類中的描述符執行個體
  5. 通過執行個體的__dict__屬性設定名為over_no_get的執行個體屬性
  6. 現在,over_no_get執行個體屬性會覆蓋描述符執行個體
  7. 為obj.over_no_get執行個體屬性賦值,仍然會經過__set__方法
  8. 讀取時,只要有同名的執行個體屬性,描述符執行個體就會被覆蓋

非覆蓋型描述符:沒有實現__set__方法的描述符稱為是非覆蓋型描述符,如果設定了同名的執行個體屬性,描述符會被覆蓋,致使描述符無法處理那個執行個體的那個屬性

>>> obj.non_over  # <1>-> NonOverriding.__get__(<NonOverriding object>, <Managed object>, <class Managed>)>>> obj.non_over = 6  # <2>>>> obj.non_over  # <3>6>>> Managed.non_over  # <4>-> NonOverriding.__get__(<NonOverriding object>, None, <class Managed>)>>> del obj.non_over  # <5>>>> obj.non_over  # <6>-> NonOverriding.__get__(<NonOverriding object>, <Managed object>, <class Managed>)

  

  1. obj.non_over觸發描述符的__get__方法,第二個參數的值是obj
  2. Managed.non_over是非覆蓋型描述符,因此沒有幹涉賦值操作的__set__方法
  3. obj有個名為non_over的執行個體屬性,把Managed類的同名描述符屬性遮蓋掉
  4. Managed.non_over描述符依然存在,會通過類截獲這次訪問
  5. 刪除non_over執行個體屬性
  6. 讀取obj.non_over時,會觸發類中描述符的__get__方法

 覆蓋類中的描述符:不管描述符是不是覆蓋型的,為類屬性賦值都能覆蓋描述符

  

>>> obj = Managed()  # <1>>>> Managed.over = 1  # <2>>>> Managed.over_no_get = 2>>> Managed.non_over = 3>>> obj.over, obj.over_no_get, obj.non_over  # <3>(1, 2, 3)

  

  1. 建立一個Managed執行個體
  2. 覆蓋類中的描述符屬性
  3. 通過執行個體訪問描述符屬性,新的值覆蓋描述符

方法是描述符:在類中定義的函數屬於Binder 方法,使用者定義的函數都有__get__方法,所以依附到類上時,就相當於描述符,方法是非覆蓋型描述符

>>> obj = Managed()>>> obj.spam  # <1><bound method Managed.spam of <descriptorkinds.Managed object at 0x00000017423284A8>>>>> Managed.spam  # <2><function Managed.spam at 0x0000001742322AE8>>>> obj.spam = 7  # <3>>>> obj.spam7

  

  1. obj.spam擷取的是Binder 方法對象
  2. Managed.spam擷取的是函數
  3. 如果為obj.spam賦值,會遮蓋類屬性,導致無法通過obj執行個體訪問spam方法

函數沒有__set__方法,因此是非覆蓋型描述符,從上面的例子來看,obj.spam和Managed.spam擷取的是不同對象。與描述符一樣,通過託管類訪問時,函數__get__方法會返回自身的引用,但是通過執行個體訪問時,函數的__get__方法返回的是Binder 方法對象:一種可調用的對象,裡麵包裝著函數,並把受管理的執行個體(如obj)綁定給函數的第一個參數(即self),這與functools.partial函數的行為一致

 為了瞭解這種機制,讓我們看下面一個例子:

import collectionsclass Text(collections.UserString):    def __repr__(self):        return ‘Text({!r})‘.format(self.data)    def reverse(self):        return self[::-1]

  

測試Text類:

>>> word = Text("forward")>>> word  # <1>Text(‘forward‘)>>> word.reverse()  # <2>Text(‘drawrof‘)>>> Text.reverse(Text(‘backward‘))  # <3>Text(‘drawkcab‘)>>> type(Text.reverse), type(word.reverse)  # <4>(<class ‘function‘>, <class ‘method‘>)>>> list(map(Text.reverse, [‘repaid‘, (10, 20, 30), Text(‘stressed‘)]))  # <5>[‘diaper‘, (30, 20, 10), Text(‘desserts‘)]>>> func = Text.reverse.__get__(word)  # <6>>>> func()  # <7>Text(‘drawrof‘)>>> Text.reverse.__get__(None, Text)  # <8><function Text.reverse at 0x000000266209E598>>>> Text.reverse<function Text.reverse at 0x000000266209E598>>>> word.reverse  # <9><bound method Text.reverse of Text(‘forward‘)>>>> Text.reverse.__get__(word)<bound method Text.reverse of Text(‘forward‘)>>>> word.reverse.__self__  # <10>Text(‘forward‘)>>> word.reverse.__func__ is Text.reverse  # <11>True

      

  1. Text執行個體的repr方法返回一個類似Text構造方法調用的字串,可用於建立相同的執行個體
  2. reverse方法返回反向拼字的單詞
  3. 在類上調用方法並傳入一個執行個體相當於調用執行個體的函數
  4. 從類擷取方法和從執行個體擷取方法的類型是不同的,一個是function,一個是method
  5. Text.reverse相當於函數,甚至可以處理Text執行個體之外的其他對象
  6. 函數都是非覆蓋型描述符。在函數上調用__get__方法時傳入執行個體,得到的是綁定到那個執行個體上的方法
  7. 我們執行第六個步驟得到的func對象,與在執行個體上調用函數效果一樣
  8. 調用函數的__get__方法時,如果instance 參數的值是None,那麼得到的是函數本身
  9. word.reverse運算式其實會調用Text.reverse.__get__(word),返回對應的Binder 方法
  10. Binder 方法對象有個__self__屬性,其值是調用這個方法的執行個體引用
  11. Binder 方法的__func__屬性是依附在託管類上那個原始函數的引用

Binder 方法對象還有個__call__方法,用於處理真正的調用過程,這個方法調用__func__屬性引用的原始函數,把函數的第一個參數設定為Binder 方法的__self__屬性,這就是形參self的隱式綁定方式

描述符用法建議:

  • 使用特性以保持簡單:內建的property類建立的其實是覆蓋型描述符,__set__方法和__get__方法都實現了,即便不定義設值方法也是如此。特性的__set__方法預設拋出AttributeError: can‘t set attribute異常,因此建立唯讀屬性最簡單的方式是使用特性
  • 唯讀描述符必須有__set__方法:如果使用描述符類實現唯讀屬性,__get__和__set__兩個方法都必須定義,否則執行個體的同名屬性會遮蓋住描述符,唯讀屬性的__set__方法只需要拋出AttributeError異常,並提供合適的錯誤資訊
  • 用於驗證的描述符可以只有__set__方法:對僅用於驗證的描述符來說,__set__方法應該檢查value參數獲得的值,如果有效,使用描述符執行個體的名稱為鍵,直接在執行個體的__dict__ 屬性中設定。這樣,從執行個體中讀取同名屬性的速度很快,因為不用經過 __get__方法處理
  • 僅有__get__方法的描述符可以實現高效緩衝:如果只編寫了__get__方法,那麼建立的是非覆蓋型描述符。這種描述符可用於執行某些耗費資源的計算,然後為執行個體設定同名屬性,緩衝結果。 同名執行個體屬性會遮蓋描述符,因此後續訪問會直接從執行個體的__dict__ 屬性中擷取值,而不會再觸發描述符的__get__方法
  • 非特殊的方法可以被執行個體屬性遮蓋:由於函數和方法只實現了__get__方法,它們不會處理同名執行個體屬性的賦值操作。因此,像 my_obj.the_method = 7這樣簡單賦值之後, 後續通過該執行個體訪問the_method得到的是數字7——但是不影響類或其他執行個體。然而,特殊方法不受這個問題的影響。解譯器只會在類中尋找特殊的方法,也就是說,repr(x)執行的其實是x.__class__.__repr__(x),因此x的__repr__屬性對repr(x)方法調用沒有影響。出於同樣的原因,執行個體的_getattr__屬性不會破壞常規的屬性訪問規則

 

Python屬性描述符(二)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.