描述器協(xié)議
描述器協(xié)議包括以下3個(gè)方法:
- object.
__get__(self, instance, owner)
調(diào)用時(shí)得到類的屬性(類屬性訪問(wèn)控制)或者類的實(shí)例的屬性(實(shí)例屬性訪問(wèn)控制),owner是屬性所在類,instance是屬性所在類的實(shí)例或None(如果通過(guò)類進(jìn)行訪問(wèn)的話)。該方法要么返回屬性值,要么拋出AttributeError異常。 - object.
__set__(self, instance, value)
調(diào)用時(shí)給實(shí)例的屬性賦予新值 - object.
__delete__(self, instance)
調(diào)用時(shí)刪除實(shí)例的屬性
描述器是什么
那什么是描述器呢?一般來(lái)講,描述器就是有“綁定行為”的對(duì)象屬性,該屬性的訪問(wèn)控制被描述器協(xié)議(__get__(),__set__(),__delete__())重寫。如果對(duì)象定義了任一個(gè)上述方法,那它就是描述器。
描述器分兩種,一種是只定義了__get__()方法的描述器叫非資料描述器(non-data descriptor),另一種是定義了__get__()和__set__()的描述器叫資料描述器(data descriptor)。
來(lái)個(gè)小例子吧
class DataDescriptor(object):
def __init__(self, val):
self.val = val
def __get__(self, instance, owner):
print "get method of %s" % self.val
return self.val
def __set__(self, instance, value):
print "set method of %s" % self.val
return self.val
class NonDataDescriptor(object):
def __init__(self, val):
self.val = val
def __get__(self, instance, owner):
print "get method of %s" % self.val
return self.val
class Context(object):
dd = DataDescriptor("data descriptor")
ndd = NonDataDescriptor("none data descriptor")
def __init__(self, val):
self.ndd = val
a = Context("haha~~")
a.dd
a.dd = "update"
a.dd
a.ndd
print(vars(Context))
print(vars(a))
print(a.ndd)
print(a.__dict__['ndd'])
print(type(a).__dict__['ndd'].__get__(a,Context))
#輸出
get method of data descriptor
set method of data descriptor
get method of data descriptor
{'__module__': '__main__', 'dd': <__main__.DataDescriptor object at 0x10445a610>, 'ndd': <__main__.NonDataDescriptor object at 0x10445a650>, '__dict__': <attribute '__dict__' of 'Context' objects>, '__weakref__': <attribute '__weakref__' of 'Context' objects>, '__doc__': None, '__init__': <function __init__ at 0x104457938>}
{'ndd': 'haha~~'}
haha~~
haha~~
get method of none data descriptor
none data descriptor
描述器的調(diào)用
為說(shuō)明描述器的調(diào)用,先看了解一下屬性的查找過(guò)程,屬性查找的過(guò)程也就是調(diào)用的過(guò)程。
實(shí)例屬性的查找過(guò)程
對(duì)于a = A(), a.attr會(huì)先調(diào)用實(shí)例的__getattribute__, 對(duì)描述器方法__get__的調(diào)用就發(fā)生在__getattribute__的內(nèi)部,如果該方法拋出AttributeError異常,而且類A有定義__getattr__方法,這時(shí)__getattr__會(huì)被調(diào)用。如果將__getattribute__內(nèi)部查找順序考慮在內(nèi),則屬性的默認(rèn)查找順序是這樣的:
- 如果attr在A或其基類的
__dict__中, 且attr是資料描述器, 那么調(diào)用其__get__方法, 否則 - 如果attr出現(xiàn)在a的
__dict__中, 那么直接返回a.__dict__['attr'], 否則 - 如果attr出現(xiàn)在A或其基類的
__dict__中- 如果attr是非資料描述器,那么調(diào)用其
__get__方法, 否則 - 返回
__dict__['attr']
- 如果attr是非資料描述器,那么調(diào)用其
- 如果A有
__getattr__方法,調(diào)用__getattr__方法,否則 - 拋出AttributeError
類屬性的查找過(guò)程
考慮到python中類也是對(duì)象,類是元類(metaclass)的實(shí)例,這樣對(duì)應(yīng)之后,類屬性的查過(guò)程與實(shí)例屬性的查找過(guò)程就基本相同了,除了第二步。由于類可能有父類,所以這里第二步就變成了:
只要A.__dict__['attr']是一個(gè)descriptor,都調(diào)用其__get__方法,否則返回A.__dict__['attr']
這一步用python描述就是:
def __getattribute__(self, key):
"Emulate type_getattro() in Objects/typeobject.c"
v = object.__getattribute__(self, key)
if hasattr(v, '__get__'):
return v.__get__(None, self)
return v
正如上面說(shuō)的,描述器是在__getattribute__內(nèi)部調(diào)用,所以重寫__getattribute__可以修改默認(rèn)的描述器調(diào)用,甚至關(guān)閉描述器的調(diào)用。另外,描述器只在新式類(繼承自object,python 2.2引入)可用。
應(yīng)用
方法(method)
python面向?qū)ο蟮奶卣魇墙⒃诨诤瘮?shù)的環(huán)境之上的,非資料描述器就是兩者之間的橋梁。函數(shù)中有__get__()方法以方便在屬性訪問(wèn)時(shí)綁定方法,也就是說(shuō)所有的函數(shù)都是非資料描述器。類的字典中將方法存成函數(shù),類定義的時(shí)候,與函數(shù)的聲明一樣,方法使用def或lambda來(lái)聲明。
在python中,類似a.b()這樣的方法調(diào)用是分兩步的:獲取屬性a.b和調(diào)用。第一步是個(gè)參數(shù)綁定的過(guò)程,返回一個(gè)可調(diào)用的bound method對(duì)象,如果不能進(jìn)行進(jìn)行參數(shù)綁定則返回unbound method對(duì)象,不可調(diào)用。
class Method(object):
name = "class name"
def __init__(self,name):
self.name = name
def instance_method(self):
return self.name
@classmethod
def class_method(cls):
return cls.name
@staticmethod
def static_method(x,y):
return x + y
- 實(shí)例方法(instancemethod)
print(Method.__dict__['instance_method'])
a = Method("haha~")
print(a.instance_method)
print(type(a).__dict__['instance_method'].__get__(a,type(a)))
print(Method.instance_method)
print(Method.__dict__['instance_method'].__get__(None,Method))
print(a.instance_method())
print(Method.instance_method(a))
#輸出
<function instance_method at 0x100a6a2a8>
<bound method Method.instance_method of <__main__.Method object at 0x100b5f590>>
<bound method Method.instance_method of <__main__.Method object at 0x100b5f590>>
<unbound method Method.instance_method>
<unbound method Method.instance_method>
haha~
haha~
實(shí)例方法調(diào)用時(shí),需要有一個(gè)實(shí)例才能綁定,對(duì)于a.instance_method()這個(gè)實(shí)例就是函數(shù)定義時(shí)的self,對(duì)于Method.instance_method(a)這個(gè)實(shí)例就很明顯了。
- 類方法
print(vars(a))
print(vars(Method))
print(a.class_method)
print(type(a).__dict__['class_method'].__get__(a,type(a)))
print(type(a).__dict__['class_method'].__get__(None,type(a)))
print(type(a).__dict__['class_method'].__get__(a,type(a))())
print(type(a).__dict__['class_method'].__get__(None,type(a))())
print(Method.class_method)
print(Method.__dict__['class_method'].__get__(None,Method))
print(Method.__dict__['class_method'].__get__(None,Method)())
#輸出
{'name': 'haha~'}
{'__dict__': <attribute '__dict__' of 'Method' objects>, '__module__': '__main__', 'static_method': <staticmethod object at 0x10d864e88>, 'name': 'class name', 'instance_method': <function instance_method at 0x10d7832a8>, '__weakref__': <attribute '__weakref__' of 'Method' objects>, 'class_method': <classmethod object at 0x10d864e50>, '__doc__': None, '__init__': <function __init__ at 0x10d7830c8>}
<bound method type.class_method of <class '__main__.Method'>>
<bound method type.class_method of <class '__main__.Method'>>
<bound method type.class_method of <class '__main__.Method'>>
class name
class name
<bound method type.class_method of <class '__main__.Method'>>
<bound method type.class_method of <class '__main__.Method'>>
class name
類方法綁定參數(shù)是類,實(shí)例并沒(méi)有用。通過(guò)實(shí)例調(diào)用時(shí),實(shí)例a中并沒(méi)有class_method屬性,按屬性的查找過(guò)程,會(huì)到類里查找,并通過(guò)非資料描述器的__get__方法調(diào)用,通過(guò)Method調(diào)用時(shí),參考前面的類屬性查找過(guò)程。
- 靜態(tài)方法
print(a.static_method)
print(type(a).__dict__['static_method'])
print(type(a).__dict__['static_method'].__get__(a,type(a)))
print(type(a).__dict__['static_method'].__get__(None,type(a)))
#輸出
<function static_method at 0x103645848>
<staticmethod object at 0x103634e18>
<function static_method at 0x103645848>
<function static_method at 0x103645848>
靜態(tài)方法是一類特殊的方法,由定義可知,靜態(tài)方法并不需要綁定到實(shí)例或類上,它只是一個(gè)普通的函數(shù),調(diào)用時(shí)不再需要返回bound method對(duì)象。
待續(xù)
參考:
https://docs.python.org/3/reference/datamodel.html
https://www.cnblogs.com/xybaby/p/6270551.html
http://www.itdecent.cn/p/250f0d305c35