Python魔術(shù)方法之描述器

摘要

七天假期最后一天边锁,假期賬戶余額已嚴(yán)重不足,那就總結(jié)下這兩天的學(xué)習(xí)內(nèi)容吧波岛。本文主要講解Python中另一種高級(jí)特性描述器茅坛,學(xué)習(xí)如何自定義描述器,和幾個(gè)內(nèi)置的描述器则拷,包括函數(shù)贡蓖、property、靜態(tài)方法和類方法煌茬。并運(yùn)用我們所學(xué)的知識(shí)斥铺,用純Python實(shí)現(xiàn)上述方法。最后介紹描述器的幾個(gè)運(yùn)用場(chǎng)景坛善,并給出真實(shí)的例子說(shuō)明晾蜘。

定義

描述器單獨(dú)出現(xiàn)是無(wú)意義的,它總是和其他類的類屬性一起出現(xiàn)浑吟,事實(shí)上笙纤,描述器描述了類屬性的訪問(wèn)、賦值和刪除行為组力,所以它被叫做描述器

描述器是怎么工作的

描述器僅僅是一個(gè)對(duì)象省容,跟其他對(duì)象相比并沒(méi)有內(nèi)在的特殊之處。就像Python其他強(qiáng)大特性一樣燎字,它們非常的簡(jiǎn)單腥椒。想要深入了解描述器協(xié)議阿宅,有三個(gè)條件需要搞清楚:

  • 你有一個(gè)新式的類
  • 它有類屬性
  • 這個(gè)類屬性有描述器的幾個(gè)方法
    上面所說(shuō)的描述器協(xié)議,包括以下幾個(gè)方法:
  • __get__(self, instance, ower) --> value
    獲得對(duì)象的屬性obj.description相當(dāng)于description.__get__(obj笼蛛, OwerClass)
    獲得類的屬性OwnerClass.descriptor相當(dāng)于descriptor.__get__(None, OwnerClass)
  • __set__(self, instance, value) --> None
    給對(duì)象賦值obj.descriptor = 5相當(dāng)于descriptor.__set__(obj, 5)
  • __delete__(self, instance)刪除對(duì)象屬性
    del obj.description 相當(dāng)于description.__delete__(obj)
    這邊有個(gè)易混淆的地方:描述器屬性被觸發(fā)是因?yàn)閷?duì)象的屬性(只有描述器對(duì)象是其他類的類屬性時(shí)候才起作用)洒放,但是在上述三個(gè)方法中self是描述器本身,而不是對(duì)象

簡(jiǎn)單的描述器

class Int:
    def __init__(self, name):
        print('Int init')
        self.name = name

    def __get__(self, instance, cls):
        print('access')
        print('{!r} {!r} {!r}'.format(self, instance, cls))
        if instance is None:
            return self
        print(instance.__dict__)
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        print('set')
        print('{!r} {!r} {!r}'.format(self, instance, value))
        instance.__dict__[self.name] = value
        print(instance.__dict__)


    def __delete__(self, instance):
        pass


class A:
    x = Int('x')
    print(x)

    def __init__(self, x):
        print('A init')
        self.x = x

# out:    在定義類A的時(shí)候就會(huì)生出類屬性x
Int init
<__main__.Int object at 0x7fcb38f916a0>

a = A(4)
# out:
A init
set
<__main__.Int object at 0x7fcb38f916a0> <__main__.A object at 0x7fcb38ff5a20> 4
{'x': 4}

a.x
# out:
access
<__main__.Int object at 0x7fcb3874aeb8> <__main__.A object at 0x7fcb386b12e8> <class '__main__.A'>
{'x': 4}
4

描述器協(xié)議非常的簡(jiǎn)單滨砍,用途十分廣泛往湿,以至于他們被打包成獨(dú)立的函數(shù)。像屬性(property)惋戏、靜態(tài)方法和類方法都是基于描述器協(xié)議的领追。

屬性(property)

下面是一個(gè)純Python實(shí)現(xiàn)的property

class Property:
    def __init__(self, fget=None, fset=None, fdel=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel

    def __get__(self, instance, cls):
        print('get')
        print(instance)
        print(self.fget)
        print(cls)
        if instance is None:
            return self
        if not callable(self.fget):
            raise AttributeError()
        print(self.fget(instance))
        return self.fget(instance)

    def __set__(self, instance, value):
        print('set')
        if not callable(self.fset):
            raise AttributeError()
        print(self.fset)
        self.fset(instance, value)

    def __delete__(self, instance):
        print('delete')
        if not callable(self.fdel):
            raise AttributeError()
        self.fdel(instance)

    def setter(self, fset):
        print('setter')
        print(fset)
        return Property(self.fget, fset, self.fdel)
    
    def deleter(self, fdel):
        return Property(self.fget, self.fset, fdel)
   

class A:
    def __init__(self, x):
        self.__x = x

    @Property
    def x(self):     # x = Property(A.x)
        return self.__x

    @x.setter  # x = x.setter(A.x) = Property(x.fget, A.x, x.fdel)
    def x(self, value):
        self.__x = value

# out:
setter
<function A.x at 0x7fcb38f9fc80>

a = A(10)

a.x
# out:
get
<__main__.A object at 0x7fcb387439b0>
<function A.x at 0x7fcb3873bd08>
<class '__main__.A'>
10

a.x = 11
# out:
set
<function A.x at 0x7fcb3873bc80>

上述代碼中我加了很多打印,能夠幫助大家理解响逢,如果覺(jué)得繁瑣绒窑,可以刪除上述打印。

  • Property對(duì)象是描述器
  • Property.setterProperty.deleter都是裝飾器舔亭,他們返回的是Property()對(duì)象些膨,不同的是@Property設(shè)置的是fgetsetterdeleter分別設(shè)置fsetfdel

類方法

純Python實(shí)現(xiàn)類方法钦铺,代碼如下:

from functools import wraps, partial

class Classmethod:
    def __init__(self, method):
        wraps(method)(self)

    def __get__(self, instance, cls):
        return partial(self.__wrapped__, cls)

class C:
    @Classmethod
    def method(cls):
        print(cls)

    def method2(self, x):
        print(cls)
        print(x)

C.method()
# out:
class method
<class '__main__.C'>

上述代碼完美的實(shí)現(xiàn)了類方法裝飾器订雾,有一個(gè)函數(shù)wraps()需要著重講解一下
wraps(fn)(g)fn的一些屬性(比如__module__, __name__, __qualname__, __doc__,__annotations__),復(fù)制給g(主要是更新g__dict__)矛洞,g就有一個(gè)屬性叫做__wrapped__葬燎,g.__wrapped__就等價(jià)于fn

描述器的一個(gè)應(yīng)用

用描述器做類型檢查

from inspect import signature

class Typed:
    def __init__(self, name, required_type):
        self.name = name
        self.required_type = required_type

    def __get__(self, instance, cls):
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if not isinstance(value, self.required_type):
            raise TypeError('{} required {}'.format(self.name, self.required_type))
        instance.__dict__[self.name] = value

def typeassert(cls):
    sig = signature(cls)
    for k, v in sig.parameters.items():
        setattr(cls, k, Typed(k, v.annotation))
    return cls

@typeassert
class Person:
    def __init__(self, name: str, age: int):  # Python3.5最新類型提示
        self.name = name
        self.age = age

p = Person(18, 'xus')
# out:
TypeError: name required <class 'str'>

結(jié)語(yǔ)

希望大家對(duì)Python描述器有了一個(gè)全新的認(rèn)識(shí),加油吧騷年缚甩!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市窑邦,隨后出現(xiàn)的幾起案子擅威,更是在濱河造成了極大的恐慌,老刑警劉巖冈钦,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件郊丛,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡瞧筛,警方通過(guò)查閱死者的電腦和手機(jī)厉熟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)较幌,“玉大人揍瑟,你說(shuō)我怎么就攤上這事≌” “怎么了绢片?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵滤馍,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我底循,道長(zhǎng)巢株,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任熙涤,我火速辦了婚禮阁苞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘祠挫。我一直安慰自己那槽,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布茸歧。 她就那樣靜靜地躺著倦炒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪软瞎。 梳的紋絲不亂的頭發(fā)上逢唤,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音涤浇,去河邊找鬼鳖藕。 笑死,一個(gè)胖子當(dāng)著我的面吹牛只锭,可吹牛的內(nèi)容都是我干的著恩。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蜻展,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼喉誊!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起纵顾,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤伍茄,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后施逾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體敷矫,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年汉额,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了曹仗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片燕侠。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡货邓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蝗锥,到底是詐尸還是另有隱情妓灌,我是刑警寧澤遭居,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布啼器,位于F島的核電站,受9級(jí)特大地震影響俱萍,放射性物質(zhì)發(fā)生泄漏端壳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一枪蘑、第九天 我趴在偏房一處隱蔽的房頂上張望损谦。 院中可真熱鬧,春花似錦岳颇、人聲如沸照捡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)栗精。三九已至,卻和暖如春瞻鹏,著一層夾襖步出監(jiān)牢的瞬間悲立,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工新博, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留薪夕,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓赫悄,卻偏偏與公主長(zhǎng)得像原献,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子埂淮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

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

  • 國(guó)家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報(bào)批稿:20170802 前言: 排版 ...
    庭說(shuō)閱讀 10,967評(píng)論 6 13
  • 1.1. 摘要 定義描述器, 總結(jié)描述器協(xié)議姑隅,并展示描述器是怎么被調(diào)用的。展示一個(gè)自定義的描述器和包括函數(shù)倔撞,屬性(...
    mutex73閱讀 452評(píng)論 0 2
  • Fluent Python Metaprogramming 部分的筆記, 在加上了其他雜七雜八的東西 Dynami...
    SkyDavid閱讀 921評(píng)論 0 0
  • 你都這樣說(shuō)了我能有什么辦法粤策,就這樣吧,如果我不累那就繼續(xù)误窖,如果我累了,那就各自安好
    薇薇安1802785閱讀 103評(píng)論 0 0
  • 如果一生中你只打算折騰vim一次或者幾次秩贰,那么認(rèn)真讀這篇文章就好了霹俺。沒(méi)錯(cuò),這就是.vimrc文件的Finnal版毒费。...
    xhat閱讀 5,974評(píng)論 1 6