python中的 descriptor
學好和用好python, descriptor是必須跨越過去的一個點,現在雖然Python書籍花樣百出,但是似乎都是在介紹一些Python庫而已,對Python語言本身的關注很少,或者即使關注了,但是能夠介紹把 dscriptor介紹清楚的,是很少的,到目前,我自己還沒有見到過。
一個attr能被稱為descriptor,除了需要定義?descriptor protocol 規定的方法外,這個attr必須是屬于某個class的,不能是屬于某個instance
一、Python中的descriptor
在一個Python class 中重寫下面任何一個方法都稱為descriptor
1.__get__(self,obj,type=None)---->value
2.__set__(self,obj,value)---->None
3.__delete__(self,obj)---->None
descriptor細分:
?1.Data descriptor : 只是重寫__get__,__set__的class
2.None Data descriptor: ?只是重寫了__get__的class
3.read-only Data descriptor ? ? 同時定義了__get__,__set__,但是這個__set__只是raise AttributeError
Data descriptor和None Data descriptor 的區別:相對于 instance 字典的優先級。?
? ? ? ? ? ?若實例字典中有與描述器同名的屬性,若描述器為資料描述器,則優先訪問資料描述器;若描述器為非資料描述器,
? ? ? ? ? ?則優先使用字典中的屬性。這條規則在實際應用中的例子:如果實例中有方法和屬性重名時,Python會優先使用實例字典中的屬性,
? ? ? ? ? ?因為實例函數的實現是個非資料描述器。
?
二、通過instance訪問屬性:
1.獲取attr
instance.a
__getattribute__,__getattr__,__get__和__dict__都與屬性訪問有關,它們的優先級:
1.當類中( type(instance) )定義了__getattribute__方法時,無條件的調用__getattribute__.所以在__getattribute__方法中,不能出現self.__attr__這種調用,它會引起無限制遞歸
2.如果訪問的attr存在,并且這個attr是屬于 type(instance)的或者屬于type(instace) 的某個父類(是super class 不是metaclass)的,并且這個attr是一個descriptor那么,此時會轉而繼續調用都相應 class.__get__。 簡而言之:
2.1 這個attr是個Descriptor,是調用這個屬性的__get__
2.2這個attr不是一個Descriptor,就調用__dict__[attr]
3.如果類中沒有定義該屬性,則調用__getattr__
4.否則,拋出異常AttributeError
?
- 實驗一 : 在self.__dict__可以獲得某個遵守了descriptor的attr,這個attr不是一個descriptor,所以不遵守descriptor規則
- 實驗二:在class.__dict__中得到attr,并且這個attr是一個descriptor
- 實驗三:__getattribute__返回非descriptor
?
class DataDescriptor(object):def __get__(self,obj,owner):print("DataDescriptor.__get__ ",self,obj,owner)return 2class A(object):datadescriptor=DataDescriptor()class B(A):def __init__(self):passdef __getattribute__(self,name):print("B.__getattribute__ name=",name)return "abc"a=B() print a.datadescriptor ''' 輸出:('B.__getattribute__ name=', 'datadescriptor') abc'''
- 實驗四: __getattribute__返回descriptor,遵守descriptor規則
- 實驗五,在找不到attr的情況下
這種情況比較特殊,在__getattribute__中return None 或者 沒有return 語句,都不會調用,只有 在__getattribute__中 raise?AttributeError(),才會調用 __getattr__,如果沒有定義__getattribute__ ,在找不到attribute的情況下,VM默認是會raise?AttributeError()的.
?代碼1
class DataDescriptor(object):def __get__(self,obj,owner):print("DataDescriptor.__get__ ",self,obj,owner)return 2class A(object):datadescriptor=DataDescriptor()class B(A):def __init__(self):passdef __getattribute__(self,name):print("B.__getattribute__ name=",name)raise AttributeError()#return Nonedef __getattr__(self,name):print("B.__getattr__ name=",name)return "Not Found"a=B() print a.datadescriptor ''' 定義了__getattribute__,但是 raise AttributeError了,所以會轉而繼續調用到__getattr__,沒有沒有 raise AttributeError,無論__getattribute__中做了什么,都不會繼續調用__getattr__ '''代碼2
class DataDescriptor(object):def __get__(self,obj,owner):print("DataDescriptor.__get__ ",self,obj,owner)return 2class A(object):datadescriptor=DataDescriptor()class B(A):def __init__(self):pass#def __getattribute__(self,name):# print("B.__getattribute__ name=",name)# raise AttributeError()#return Nonedef __getattr__(self,name):print("B.__getattr__ name=",name)return "Not Found"a=B() print a.zz ''' 找不到zz 這個attr,vm默認會 raise AttributeError,自動轉而調用__getattr__ '''2.設置instance.attr?
設置instance.attr=value時,涉及到三個方法,分別為__setattr__、__set__和__dict__[attr]=val,沒有__setattribute__
? ?調用的優先級為:
1.如果type(instance) 中定義了__setattr__方法,就直接調用這個方法。
2.如果這個attr是個descriptor,那會分情況:
2.1,如果是個data descriptor(定義了 __set__方法),那么會調用 data descriptor的__set__方法
2.2,如果是個None data descriptor(沒有定義__set__方法),那么會是instance.__dict__[attr]=value
3.如果attr不是descriptor,會直接instance.__dict__[attr]=value
? 實驗一:None data descriptor時的設置
# -*- coding:utf-8 -*- class DataDescriptor(object):def __init__(self):self.values={};def __get__(self,obj,owner):print("DataDescriptor.__get__ ",self,obj,owner)return self.valuesclass A(object):datadescriptor=DataDescriptor()class B(A):def __init__(self):passa=B() a.datadescriptor=999 print a.__dict__''' 輸出: {'datadescriptor': 999} '''
實驗二:Data descriptor時的set attr
# -*- coding:utf-8 -*- class DataDescriptor(object):def __init__(self):self.values={};def __get__(self,obj,owner):print("DataDescriptor.__get__ ",self,obj,owner)return self.valuesdef __set__(self,instance,value):print("DataDescriptor.__set__ ",instance,value)class A(object):datadescriptor=DataDescriptor()class B(A):def __init__(self):passa=B() a.datadescriptor=999 print a.__dict__''' 輸出: ('DataDescriptor.__set__ ', <__main__.B object at 0x00BD8E30>, 999) {} '''可以看出在data descriptor時,設置相應的data descriptor attribute時,沒有影響到instance.__dict__
實驗三:type(instance)有定義__setattr__方法時:
# -*- coding:utf-8 -*- class DataDescriptor(object):def __init__(self):self.values={};def __get__(self,obj,owner):print("DataDescriptor.__get__ ",self,obj,owner)return self.valuesdef __set__(self,instance,value):print("DataDescriptor.__set__ ",instance,value)class A(object):datadescriptor=DataDescriptor()class B(A):def __init__(self):passdef __setattr__(self,key,value):print("B.__setattr__ ",key,value)self.__dict__[key]=valuea=B() a.datadescriptor=999 print a.__dict__''' 輸出: ('B.__setattr__ ', 'datadescriptor', 999) {'datadescriptor': 999} '''
當type(instance)有定義__setattr__方法時,那么是否是 descriptor就無關緊要了,都會調用這個__setattr__
2,刪除instance.attr?
刪除instance.attr和設置instacne.attr的情況非常類似,涉及到三個方法或情況:__delattr__或__delete__ , 刪除 instance.__dict__
優先級也是和設置instance.attr一樣的:
1.如果type(instance)定義了__delattr__,那么直接調用,無論這個attr是否為descriptor
2.如果沒有定義__delattr__,并且是descriptor
2.1,如果這個descriptor 定義了 __delete__,那么調用__delete__方法
2.2如果這個descriptor 沒有定義__delete__,那么raise?AttributeError
3.del intance.__dict__[attr]
?
三、通過class訪問屬性
通過class object來獲取attr在概念上其實和通過instance來獲取屬性是一樣的,instance 的class 是某個class object,而 class object 的class 應該是這個class的 metaclass
當在class object 的dict中找不到attr時,會轉而向 class 的metaclass的dict中去尋找.
通過ClassA.attr訪問屬性的規則為:
- ?實驗
?
其實可以發現descriptor的主要作用是起到了保護作用,當某種類型的變量被訪問的時候,在給一次程序員一個控制的機會。
另外__getattr__也有類似的作用,__getattr__的用法有很多,典型的是在 web程序中,經常要有request.attr 、request[attr]這種操作,那么這個時候,把本需要用函數(類似 request.get(name) )來獲取某些狀態變量的操作,轉成?request.attr 、request[attr]這種形式,方便很多。
?
轉載于:https://www.cnblogs.com/hi0xcc/p/5586601.html
總結
以上是生活随笔為你收集整理的python中的 descriptor的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++服务器设计(七):聊天系统服务端实
- 下一篇: python基础学习1-三元表达式和la