Python 描述器解析

語法簡析

一般來說,描述器(descriptor)是一個有”綁定行為”的對象屬性(object attribute)妓忍,它的屬性訪問被描述器協(xié)議方法重寫。這些方法是 __get__()蔗怠、 __set__()__delete__() 厢拭。如果一個對象定義了以上任意一個方法拴鸵,它就是一個描述器玷坠。而描述器協(xié)議的具體形式如下:

descr.__get__(self, obj, type=None) --> value

descr.__set__(self, obj, value) --> None

descr.__delete__(self, obj) --> None

描述器本質(zhì)上是一個類對象,該對象定義了描述器協(xié)議三種方法中至少一種劲藐。而這三種方法只有當類的實例出現(xiàn)在一個所有者類(owner class)之內(nèi)時才有效八堡,也就是說,描述器必須出現(xiàn)在所有者類或其父類的字典 __dict__ 里聘芜。這里提到了兩個類兄渺,一是定義了描述器協(xié)議的描述器類,另一個是使用描述器的所有者類厉膀。

描述器往往以裝飾器的方式被使用溶耘,導(dǎo)致二者常被混淆二拐。描述器類和不帶參數(shù)的裝飾器類一樣服鹅,都傳入函數(shù)對象作為參數(shù),并返回一個類實例百新,所不同的是企软,裝飾器類返回 callable 的實例,描述器則返回描述器實例饭望。

記住上面的話仗哨,下面我們舉例說明。

@Property

Python 內(nèi)置的 property 函數(shù)可以說是最著名的描述器之一铅辞,幾乎所有講述描述器的文章都會拿它做例子厌漂。

property 是用 C 實現(xiàn)的,不過這里有一份等價的 Python 實現(xiàn):

class Property(object):
    "Emulate PyProperty_Type() in Objects/descrobject.c"

    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(obj, value)

    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(obj)

    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel, self.__doc__)

    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel, self.__doc__)

    def deleter(self, fdel):
        return type(self)(self.fget, self.fset, fdel, self.__doc__)

Property 怎么用呢斟珊?看下面的例子:

class C(object):
    def __init__(self):
        self._x = None

    @Property
    def x(self):
        """I'm the 'x' property."""
        return self._x

    @x.setter
    def x(self, value):
        assert value > 0
        self._x = value

    @x.deleter
    def x(self):
        del self._x

我們結(jié)合源代碼和用法來分析 Property苇倡。

@Property 的用法就是一個裝飾器。我們可以將其等價轉(zhuǎn)化為:

x = Property(x)

函數(shù) x 作為位置參數(shù)被賦給 Property.__init__()fget囤踩,得到新的 x 已經(jīng)不是個函數(shù)而是個完整實現(xiàn)了 __get__() 方法的描述器實例了旨椒。

@x.setter 的用法略有不同。它實際上是利用上面定義的描述器實例 xsetter 方法堵漱,重新創(chuàng)建了新的實例综慎。這時變量 x 再次被更新,指向了一個完整實現(xiàn) __get__()__set__() 方法的新描述器勤庐。傳入 setter 方法的函數(shù)名必須是 x示惊,否則如果是 y好港,按照裝飾器的性質(zhì),

y = x.setter(y)

新描述器就被 y 引用了米罚,與需求不符媚狰。

Property 提供了像訪問類“成員變量”一樣訪問 get、set 方法的能力阔拳。

In [123]: c = C()

In [124]: c.x = 1

In [125]: c.x
Out[125]: 1

In [126]: c.x = 0
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-126-b03deb420dcb> in <module>()
----> 1 c.x = 0

<ipython-input-50-95b8686aa4bd> in __set__(self, obj, value)
     20         if self.fset is None:
     21             raise AttributeError("can't set attribute")
---> 22         self.fset(obj, value)
     23
     24     def __delete__(self, obj):

<ipython-input-116-379a4e5fa639> in x(self, value)
     10     @x.setter
     11     def x(self, value):
---> 12         assert value > 0
     13         self._x = value
     14

AssertionError:

與一般的屬性訪問不同崭孤,c.x 訪問的已經(jīng)不是簡單的屬性,而是相當于 x.__get__(c)糊肠,可以調(diào)用各種復(fù)雜方法對屬性作檢查辨宠、包裝 。

那么货裹,描述器是怎樣被訪問到的呢嗤形?

調(diào)用描述器

有兩類描述器:如果同時定義了 __get__()__set__() 方法的描述器稱為資料描述器(data descriptor),僅定義了 __get__() 的描述器稱為非資料描述器(non-data descriptor)弧圆。非資料描述器常用于類的方法赋兵,如常見的 staticmethodclassmethod,都是其應(yīng)用搔预。

如前文所說霹期,描述器常在所有者類或其實例中被調(diào)用。

對于實例對象拯田,object.__getattribute__() 會把 c.x 轉(zhuǎn)化為 type(c).__dict__['x'].__get__(c, type(c))历造。如果實例中有和描述器重名的屬性 x 怎么辦?資料和非資料描述器的區(qū)別在于船庇,相對于實例字典的優(yōu)先級不同吭产。當描述器和實例字典中的某個屬性重名,按訪問優(yōu)先級鸭轮,資料描述器 > 同名實例字典中的屬性 > 非資料描述器臣淤,優(yōu)先級小的會被大的覆蓋。上面的類 C 中窃爷,會優(yōu)先訪問資料描述器 x邑蒋。下面將講到,類的方法實際就是一個僅實現(xiàn)了 __get__() 的非資料描述器吞鸭,所以如果實例 c 中同時定義了名為 foo 的方法和屬性寺董,那么 c.foo 訪問的是屬性而非方法。

對于類刻剥,type.__getattribute__()C.x 轉(zhuǎn)化為 C.__dict__['x'].__get__(None, C)遮咖。

有幾點需要牢記的:

  1. 描述器被 __getattribute__() 方法調(diào)用
  2. 因而,重載 __getattribute__() 可能會妨礙描述器被自動調(diào)用
  3. __getattribute__() 僅存在于繼承自 object 的新式類之中
  4. object.__getattribute__()type.__getattribute__()__get__() 的調(diào)用不一樣
  5. 資料描述器總會覆蓋實例字典造虏,即資料描述器具有最高優(yōu)先級
  6. 非資料描述器可能會被實例字典覆蓋御吞,即非資料描述器具有最低優(yōu)先級

非資料描述器與類方法

Python 面向?qū)ο蟮奶卣鹘⒃诨诤瘮?shù)的環(huán)境之上麦箍。Python 用非資料描述器將二者無縫結(jié)合。

方法和普通函數(shù)唯一的區(qū)別就是陶珠,一般方法的第一個參數(shù)引用了當前實例挟裂,即通常命名為 self 的變量。

Python 中的函數(shù)揍诽,可以被認為是一個實現(xiàn)了 __get__() 的非資料描述器诀蓉,用 Python 來描述就是:

class Function(object):
    . . .
    def __get__(self, obj, objtype=None):
        "Simulate func_descr_get() in Objects/funcobject.c"
        return types.MethodType(self, obj, objtype)

當函數(shù)作為屬性被訪問時,非資料描述器把函數(shù)變?yōu)橐粋€方法暑脆,把實例調(diào)用 obj.f(*args) 轉(zhuǎn)化成 f(obj, *args)渠啤,把類調(diào)用 klass.f(*args) 轉(zhuǎn)化為 f(*args)

更多綁定和轉(zhuǎn)換參見下表添吗。

轉(zhuǎn)換 從對象調(diào)用 從類調(diào)用
函數(shù) f(obj, *args) f(*args)
靜態(tài)方法 f(*args) f(*args)
類方法 f(type(obj), *args) f(klass, *args)

靜態(tài)方法是特殊的方法沥曹,可以無須實例化而在類中被直接調(diào)用,這時當然無法提供合法的 self碟联。為此妓美,需要實現(xiàn) staticmethod 描述器,其 __get__() 返回的函數(shù)無需實例參數(shù)鲤孵,其實也就是原樣返回即可壶栋,可以用 Python 這樣實現(xiàn)

class StaticMethod(object):
    "Emulate PyStaticMethod_Type() in Objects/funcobject.c"

    def __init__(self, f):
        self.f = f

    def __get__(self, obj, objtype=None):
        return self.f

類方法是另一種特殊的方法,無需當前實例 self裤纹, 但是需要當前類 klass (通常也寫成 cls)委刘,純 Python 實現(xiàn)如下:

class ClassMethod(object):
    "Emulate PyClassMethod_Type() in Objects/funcobject.c"

    def __init__(self, f):
        self.f = f

    def __get__(self, obj, klass=None):
        if klass is None:
            klass = type(obj)
        def newfunc(*args):
            return self.f(klass, *args)
        return newfunc

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末丧没,一起剝皮案震驚了整個濱河市鹰椒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鹊漠,老刑警劉巖格遭,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件志于,死亡現(xiàn)場離奇詭異,居然都是意外死亡奸汇,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門往声,熙熙樓的掌柜王于貴愁眉苦臉地迎上來擂找,“玉大人,你說我怎么就攤上這事浩销」嵯眩” “怎么了?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵慢洋,是天一觀的道長塘雳。 經(jīng)常有香客問我陆盘,道長,這世上最難降的妖魔是什么败明? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任隘马,我火速辦了婚禮,結(jié)果婚禮上妻顶,老公的妹妹穿的比我還像新娘酸员。我一直安慰自己,他們只是感情好讳嘱,可當我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布沸呐。 她就那樣靜靜地躺著,像睡著了一般呢燥。 火紅的嫁衣襯著肌膚如雪崭添。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天叛氨,我揣著相機與錄音呼渣,去河邊找鬼。 笑死寞埠,一個胖子當著我的面吹牛屁置,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播仁连,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼蓝角,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了饭冬?” 一聲冷哼從身側(cè)響起使鹅,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎昌抠,沒想到半個月后患朱,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡炊苫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年裁厅,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片侨艾。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡执虹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出唠梨,到底是詐尸還是另有隱情袋励,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站插龄,受9級特大地震影響愿棋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜均牢,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一糠雨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧徘跪,春花似錦甘邀、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至哨查,卻和暖如春逗抑,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背寒亥。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工邮府, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人溉奕。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓褂傀,卻偏偏與公主長得像,于是被迫代替她去往敵國和親加勤。 傳聞我的和親對象是個殘疾皇子仙辟,可洞房花燭夜當晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)鳄梅,斷路器叠国,智...
    卡卡羅2017閱讀 134,651評論 18 139
  • 1.1. 摘要 定義描述器, 總結(jié)描述器協(xié)議,并展示描述器是怎么被調(diào)用的卫枝。展示一個自定義的描述器和包括函數(shù)煎饼,屬性(...
    mutex73閱讀 452評論 0 2
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法校赤,內(nèi)部類的語法,繼承相關(guān)的語法筒溃,異常的語法马篮,線程的語...
    子非魚_t_閱讀 31,622評論 18 399
  • 本文翻譯自python descriptor guide 摘要 本文定義了描述符,總結(jié)了其中的協(xié)議怜奖,并且介紹如何調(diào)...
    大蟒傳奇閱讀 1,174評論 0 5
  • 使用單獨mongo命令載入相應(yīng)mongo配置文件 mongod -f /etc/mongo.confmongod ...
    Zhang21閱讀 943評論 0 1