學好和用好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 DataDescriptor(object):def __get__(self,obj,owner):print("datadescriptor.__get__ ",self,obj,owner)return 2class A(object):passclass B(A):def __init__(self):self.datadescriptor=DataDescriptor()a=B()
print a.datadescriptor
#輸出<__main__.DataDescriptor object at 0x00BD8DB0>
- 實驗二:在class.__dict__中得到attr,并且這個attr是一個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):passa=B()
print a.datadescriptor
'''
輸出 ('datadescriptor.__get__ ', <__main__.DataDescriptor object at 0x00BD8CF0>, <__main__.B object at 0x00BD8D50>,<class '__main__.B'>)
'''
- 實驗三:__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規則
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 type(self).datadescriptora=B()
print a.datadescriptor'''
輸出:
('B.__getattribute__ name=', 'datadescriptor')
('DataDescriptor.__get__ ', <__main__.DataDescriptor object at 0x00BD8CB0>, None, <class '__main__.B'>)
2
'''
- 實驗五,在找不到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訪問屬性的規則為:
- 如果MetaClass中有__getattribute__,則直接返回該__getattribute__的結果。
- 如果attr是個Descriptor,則直接返回Descriptor的__get__的結果。
- 如果attr是class.dict中的屬性,則直接返回attr的值
- 如果類中沒有attr,且MetaClass中定義了__getattr__,則調用MetaClass中的__getattr__
- 如果類中沒有attr,且MetaClass中沒有定義__getattr__,則拋出異常AttributeError
- ?實驗
class Metaclass(type):datadescriptor=DataDescriptor()def __new__(metaclz,name,bases,attrs):print("create new class ",metaclz,name)return type.__new__(metaclz, name, bases, attrs)def __getattr__(self,name):print("Metaclass.__getattr__ name:",name)#def __getattribute__(self,name):# print("Metaclass.__getattribute__ name:",name)# return name+'a'class classB(object):__metaclass__=Metaclassprint classB.datadescriptorprint classB.ss
'''
輸出('create new class ', <class '__main__.Metaclass'>, 'classB')
('DataDescriptor.__get__ ', <__main__.DataDescriptor object at 0x00BD8EF0>, <class '__main__.classB'>, <class
'__main__.Metaclass'>)
2
('Metaclass.__getattr__ name:', 'ss')
None'''
?
其實可以發現descriptor的主要作用是起到了保護作用,當某種類型的變量被訪問的時候,在給一次程序員一個控制的機會。
另外__getattr__也有類似的作用,__getattr__的用法有很多,典型的是在 web程序中,經常要有request.attr 、request[attr]這種操作,那么這個時候,把本需要用函數(類似 request.get(name) )來獲取某些狀態變量的操作,轉成?request.attr 、request[attr]這種形式,方便很多。
?