深入解析Python中的descriptor描述器的作用及用法
一般來(lái)說(shuō),一個(gè)描述器是一個(gè)有“綁定行為”的對(duì)象屬性(object attribute),它的訪問(wèn)控制被描述器協(xié)議方法重寫。這些方法是 __get__(), __set__(), 和 __delete__() 。有這些方法的對(duì)象叫做描述器。
默認(rèn)對(duì)屬性的訪問(wèn)控制是從對(duì)象的字典里面(__dict__)中獲取(get), 設(shè)置(set)和刪除(delete)它。舉例來(lái)說(shuō), a.x 的查找順序是, a.__dict__['x'] , 然后 type(a).__dict__['x'] , 然后找 type(a) 的父類(不包括元類(metaclass)).如果查找到的值是一個(gè)描述器, Python就會(huì)調(diào)用描述器的方法來(lái)重寫默認(rèn)的控制行為。這個(gè)重寫發(fā)生在這個(gè)查找環(huán)節(jié)的哪里取決于定義了哪個(gè)描述器方法。注意, 只有在新式類中時(shí)描述器才會(huì)起作用。(新式類是繼承自 type 或者 object 的類)
描述器是強(qiáng)大的,應(yīng)用廣泛的。描述器正是屬性, 實(shí)例方法, 靜態(tài)方法, 類方法和 super 的背后的實(shí)現(xiàn)機(jī)制。描述器在Python自身中廣泛使用,以實(shí)現(xiàn)Python 2.2中引入的新式類。描述器簡(jiǎn)化了底層的C代碼,并為Python的日常編程提供了一套靈活的新工具。
描述器協(xié)議
descr.__get__(self, obj, type=None) --> value descr.__get__(self, obj, value) --> None descr.__delete__(self, obj) --> None
一個(gè)對(duì)象如果是一個(gè)描述器,被當(dāng)做對(duì)象屬性(很重要)時(shí)重寫默認(rèn)的查找行為。
如果一個(gè)對(duì)象同時(shí)定義了__get__和__set__,它叫data descriptor。僅定義了__get__的描述器叫non-data descriptor。
data descriptor和non-data descriptor區(qū)別在于: 相對(duì)于實(shí)例的字典的優(yōu)先級(jí),如果實(shí)例字典有與描述器具同名的屬性,如果描述器是data descriptor,優(yōu)先使用data descriptor。如果是non-data descriptor,優(yōu)先使用字典中的屬性。
class B(object): def __init__(self): self.name = 'mink' def __get__(self, obj, objtype=None): return self.name class A(object): name = B() a = A() print a.__dict__ # print {} print a.name # print mink a.name = 'kk' print a.__dict__ # print {'name': 'kk'} print a.name # print kk
這里B是一個(gè)non-data descriptor所以當(dāng)a.name = 'kk'的時(shí)候,a.__dict__里會(huì)有name屬性, 接下來(lái)給它設(shè)置__set__
def __set__(self, obj, value): self.name = value ... do something a = A() print a.__dict__ # print {} print a.name # print mink a.name = 'kk' print a.__dict__ # print {} print a.name # print kk
因?yàn)閐ata descriptor訪問(wèn)屬性優(yōu)先級(jí)比實(shí)例的字典高,所以a.__dict__是空的。
描述器的調(diào)用
描述器可以直接這么調(diào)用: d.__get__(obj)
然而更常見(jiàn)的情況是描述器在屬性訪問(wèn)時(shí)被自動(dòng)調(diào)用。舉例來(lái)說(shuō), obj.d 會(huì)在 obj 的字典中找 d ,如果 d 定義了 __get__ 方法,那么 d.__get__(obj) 會(huì)依據(jù)下面的優(yōu)先規(guī)則被調(diào)用。
調(diào)用的細(xì)節(jié)取決于 obj 是一個(gè)類還是一個(gè)實(shí)例。另外,描述器只對(duì)于新式對(duì)象和新式類才起作用。繼承于 object 的類叫做新式類。
對(duì)于對(duì)象來(lái)講,方法 object.__getattribute__() 把 b.x 變成 type(b).__dict__['x'].__get__(b, type(b)) 。具體實(shí)現(xiàn)是依據(jù)這樣的優(yōu)先順序:資料描述器優(yōu)先于實(shí)例變量,實(shí)例變量?jī)?yōu)先于非資料描述器,__getattr__()方法(如果對(duì)象中包含的話)具有最低的優(yōu)先級(jí)。完整的C語(yǔ)言實(shí)現(xiàn)可以在 Objects/object.c 中 PyObject_GenericGetAttr() 查看。
對(duì)于類來(lái)講,方法 type.__getattribute__() 把 B.x 變成 B.__dict__['x'].__get__(None, B) 。用Python來(lái)描述就是:
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
其中重要的幾點(diǎn):
- 描述器的調(diào)用是因?yàn)?__getattribute__()
- 重寫 __getattribute__() 方法會(huì)阻止正常的描述器調(diào)用
- __getattribute__() 只對(duì)新式類的實(shí)例可用
- object.__getattribute__() 和 type.__getattribute__() 對(duì) __get__() 的調(diào)用不一樣
- 資料描述器總是比實(shí)例字典優(yōu)先。
- 非資料描述器可能被實(shí)例字典重寫。(非資料描述器不如實(shí)例字典優(yōu)先)
- super() 返回的對(duì)象同樣有一個(gè)定制的 __getattribute__() 方法用來(lái)調(diào)用描述器。調(diào)用 super(B, obj).m() 時(shí)會(huì)先在 obj.__class__.__mro__ 中查找與B緊鄰的基類A,然后返回 A.__dict__['m'].__get__(obj, A) 。如果不是描述器,原樣返回 m 。如果實(shí)例字典中找不到 m ,會(huì)回溯繼續(xù)調(diào)用 object.__getattribute__() 查找。(譯者注:即在 __mro__ 中的下一個(gè)基類中查找)
注意:在Python 2.2中,如果 m 是一個(gè)描述器, super(B, obj).m() 只會(huì)調(diào)用方法 __get__() 。在Python 2.3中,非資料描述器(除非是個(gè)舊式類)也會(huì)被調(diào)用。 super_getattro() 的實(shí)現(xiàn)細(xì)節(jié)在: Objects/typeobject.c ,[del] 一個(gè)等價(jià)的Python實(shí)現(xiàn)在 Guido's Tutorial [/del] (譯者注:原文此句已刪除,保留供大家參考)。
以上展示了描述器的機(jī)理是在 object, type, 和 super 的 __getattribute__() 方法中實(shí)現(xiàn)的。由 object 派生出的類自動(dòng)的繼承這個(gè)機(jī)理,或者它們有個(gè)有類似機(jī)理的元類。同樣,可以重寫類的 __getattribute__() 方法來(lái)關(guān)閉這個(gè)類的描述器行為。
描述器例子
下面的代碼中定義了一個(gè)資料描述器,每次 get 和 set 都會(huì)打印一條消息。重寫 __getattribute__() 是另一個(gè)可以使所有屬性擁有這個(gè)行為的方法。但是,描述器在監(jiān)視特定屬性的時(shí)候是很有用的。
class RevealAccess(object): """A data descriptor that sets and returns values normally and prints a message logging their access. """ def __init__(self, initval=None, name='var'): self.val = initval self.name = name def __get__(self, obj, objtype): print 'Retrieving', self.name return self.val def __set__(self, obj, val): print 'Updating' , self.name self.val = val >>> class MyClass(object): x = RevealAccess(10, 'var "x"') y = 5 >>> m = MyClass() >>> m.x Retrieving var "x" 10 >>> m.x = 20 Updating var "x" >>> m.x Retrieving var "x" 20 >>> m.y 5
這個(gè)協(xié)議非常簡(jiǎn)單,并且提供了令人激動(dòng)的可能。一些用途實(shí)在是太普遍以致于它們被打包成獨(dú)立的函數(shù)。像屬性(property), 方法(bound和unbound method), 靜態(tài)方法和類方法都是基于描述器協(xié)議的。
- DES加密解密算法之python實(shí)現(xiàn)版(圖文并茂)
- Python3對(duì)稱加密算法AES、DES3實(shí)例詳解
- python3+PyQt5圖形項(xiàng)的自定義和交互 python3實(shí)現(xiàn)page Designer應(yīng)用程序
- python3+PyQt5+Qt Designer實(shí)現(xiàn)擴(kuò)展對(duì)話框
- python3+PyQt5+Qt Designer實(shí)現(xiàn)堆疊窗口部件
- 詳解Python 序列化Serialize 和 反序列化Deserialize
- python基于pyDes庫(kù)實(shí)現(xiàn)des加密的方法
- python實(shí)現(xiàn)DES加密解密方法實(shí)例詳解
- Python Des加密解密如何實(shí)現(xiàn)軟件注冊(cè)碼機(jī)器碼
相關(guān)文章
Python使用enumerate獲取迭代元素下標(biāo)
這篇文章主要介紹了python使用enumerate獲取迭代元素下標(biāo),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02Python使用Pyqt5實(shí)現(xiàn)簡(jiǎn)易瀏覽器(最新版本測(cè)試過(guò))
這篇文章主要介紹了Python使用Pyqt5實(shí)現(xiàn)簡(jiǎn)易瀏覽器(最新版本測(cè)試過(guò)),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04pytorch 中autograd.grad()函數(shù)的用法說(shuō)明
這篇文章主要介紹了pytorch 中autograd.grad()函數(shù)的用法說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-05-05Python3.4 splinter(模擬填寫表單)使用方法
今天小編就為大家分享一篇Python3.4 splinter(模擬填寫表單)使用方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-10-10Python 如何定義匿名或內(nèi)聯(lián)函數(shù)
這篇文章主要介紹了Python 如何定義匿名或內(nèi)聯(lián)函數(shù),文中講解非常細(xì)致,代碼幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下2020-08-08python讀取文件由于編碼問(wèn)題失敗匯總以及解決辦法
這篇文章主要給大家介紹了關(guān)于python讀取文件由于編碼問(wèn)題失敗匯總以及解決辦法的相關(guān)資料,文件編碼錯(cuò)誤指的是在Python讀取文件的過(guò)程中出現(xiàn)的編碼不匹配的問(wèn)題,需要的朋友可以參考下2023-10-10