python描述器(Descriptor)

描述器協(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']
  • 如果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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,586評(píng)論 19 139
  • 國(guó)家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報(bào)批稿:20170802 前言: 排版 ...
    庭說(shuō)閱讀 12,438評(píng)論 6 13
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類相關(guān)的語(yǔ)法,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚_t_閱讀 34,734評(píng)論 18 399
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,068評(píng)論 0 9
  • 認(rèn)識(shí)我的人都知道我是一個(gè)典型的雙子座性格。雙子座的人善變,也因此被貼上了各種各樣的標(biāo)簽。 不過(guò)今天我突然領(lǐng)悟這其實(shí)...
    樊語(yǔ)朱心閱讀 660評(píng)論 0 1

友情鏈接更多精彩內(nèi)容