Python基礎27-面向?qū)ο?系統(tǒng)內(nèi)置方法7-描述器)

7 描述器

1 概念

  • 用于描述一個屬性對應操作的對象。
  • 屬性對應操作一般為:增/改示辈、刪、查

2 作用

  • 可以代為管理一個類屬性的讀寫刪操作, 在相關方法中, 對數(shù)據(jù)進行驗證處理, 過濾處理等等
  • 如果一個類屬性被定義為描述器魄幕,那么以后對這個類屬性的操作(讀寫刪), 都將由這個描述器代理

3 定義

3.1 通過 property創(chuàng)建屬性的描述器

其實就是前面提到的 property 的使用

  • 一般我們定義類都會將屬性定義為私有屬性躏救,這樣外界就不能隨便訪問或賦值
class Person:
    def __init__(self, value):
        self.__age = value
  • 讓實例能夠通過.age方式進行訪問或過濾性修改
  • 通過 property 定義后返回的 age 就是一個描述器,描述器就是一個對屬性操作進行描述(即過濾或保護等)的對象

class Person:
    def __init__(self, value):
        self.__age = value

    def get_age(self):
        print("get age")
        return self.__age

    def set_age(self, value):
        print("set age")
        if value < 0:
            value = 0
        self.__age = value

    def del_age(self):
        print("del age")
        del self.__age

    # 此時返回的 age 就是一個描述器肌访,描述器就是一個對屬性操作進行描述(即過濾或保護等)的對象
    age = property(get_age, set_age, del_age)

p = Person()
# 訪問
print(p.age)
# 賦值
p.age = 19
# 刪除
del p.age

  • 查看上面代碼中通過property 定義的 age 描述器與 name 屬性分區(qū)對別
    age 是被分配到 Data descriptors defined here: 區(qū)
class Person:
    # 與 age 對比用
    name = "fkm"

    def __init__(self, value):
        self.__age = value

    def get_age(self):
        print("get age")
        return self.__age

    def set_age(self, value):
        print("set age")
        if value < 0:
            value = 0
        self.__age = value

    def del_age(self):
        print("del age")
        del self.__age

    age = property(get_age, set_age, del_age)

p = Person(10)
print(Person.__dict__)
print(p.__dict__)

print("-" * 20)

help(Person)



>>>> 打印結果

{
'__module__': '__main__', 
'name': 'fkm', 
'__init__': <function Person.__init__ at 0x10c86ebf8>, 
'get_age': <function Person.get_age at 0x10c86eb70>, 
'set_age': <function Person.set_age at 0x10c86ec80>, 
'del_age': <function Person.del_age at 0x10c86ed08>, 
'age': <property object at 0x10c6bea98>, 
'__dict__': <attribute '__dict__' of 'Person' objects>, 
'__weakref__': <attribute '__weakref__' of 'Person' objects>, 
'__doc__': None
}

{'_Person__age': 10}
--------------------
Help on class Person in module __main__:

class Person(builtins.object)
 |  Methods defined here:
 |  
 |  __init__(self, value)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |  
 |  del_age(self)
 |  
 |  get_age(self)
 |  
 |  set_age(self, value)
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)
 |  
 |  age
 |  
 |  ----------------------------------------------------------------------
 |  Data and other attributes defined here:
 |  
 |  name = 'fkm'


Process finished with exit code 0

  • 使用 property 內(nèi)置方式找默,同樣可以通過屬性描述器訪問屬性
class Person:
    def __init__(self):
        self.__age = 10

    @property
    def age(self):
        return self.__age

    @age.setter
    def age(self, value):
        if value < 0:
            value = 0
        self.__age = value

    @age.deleter
    def age(self):
        print("del age")
        del self.__age

# p = Person()
# # 訪問
# print(p.age)
# # 賦值
# p.age = 19
# # 刪除
# del p.age

help(Perosn)
# age 同樣在 Data descriptors defined here: 區(qū)

>>>>> 打印結果

Help on class Person in module __main__:

class Person(builtins.object)
 |  Methods defined here:
 |  
 |  __init__(self)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)
 |  
 |  age


Process finished with exit code 0

3.2 通過屬性實例化方式 - 創(chuàng)建屬性描述器

3.2.1 該實例的類必須要實現(xiàn)以下三個方法

__get__
__set__
__delete__
  • 優(yōu)點
  • 上述3.1創(chuàng)建屬性描述器方式,具有導致該類臃腫的弊端吼驶,試想:實現(xiàn)一個 age 屬性描述器已經(jīng)在 Person 類里面寫了3個方法了惩激,如果再多些屬性,則會出現(xiàn)3*x 個方法蟹演。
  • 而通過屬性實例化方式风钻,則可以將對該屬性進行操作描述的3個方法抽離到該實例類里面,更加面向?qū)ο罅恕?/li>
  • 這樣的屬性實例化方式也體現(xiàn) python 語言一切皆對象的特性
# 屬性描述器對象
class Age:
    def __get__(self, instance, owner):
        print("get")

    def __set__(self, instance, value):
        print("set")

    def __delete__(self, instance):
        print("delete")


# 類
class Person:
    age = Age() # 屬性描述器實例化

# 此時 age 也是一個類屬性酒请,但之能通過類執(zhí)行 get 方法骡技,其他方法不會被轉(zhuǎn)傳到描述器中

# 屬性操作
p = Person()
p.age = 10
print(p.age)
# del p.age

>>> 打印結果

set
get
None

3.2.2 通過屬性實例化調(diào)用描述器時,使用注意事項:

  1. 使用實例進行調(diào)用
    最多三個方法都會被調(diào)用

  2. 使用類進行調(diào)用
    最多會調(diào)用get方法

  3. 不能順利轉(zhuǎn)換場景
    3.1 新式類和經(jīng)典類
    描述器僅在新式類(繼承自 object 的)中生效羞反,且類及描述器類都是新式類

    3.2 方法攔截
    * 一個實例屬性的正常訪問順序

    1 實例對象自身的__dict__字典
    2 對應類對象的__dict__字典
    3 如果有父類, 會再往上層的__dict__字典中檢測
    4 如果沒找到, 又定義了__getattr__方法, 就會調(diào)用這個方法
    
    • 而在上述的整個過程當中, 是如何將描述器的__get__方法給嵌入到查找機制當中?

    • 就是通過這個方法進行實現(xiàn):__getattribute__

    • 內(nèi)部實現(xiàn)模擬
      如果實現(xiàn)了描述器方法get就會直接調(diào)用
      如果沒有, 則按照上面的機制去查找

4 描述器-和實例屬性同名時, 操作優(yōu)先級

  • 資料描述器:描述器類同時實現(xiàn)了 get set 方法

  • 非資料描述器:僅僅實現(xiàn)了 get 方法

  • 資料描述器 > 實例屬性 > 非資料描述器

  • 1 資料描述器 > 實例屬性 測試

class Age(object):
    def __get__(self, instance, owner):
        print("get")

    def __set__(self, instance, value):
        print("set")

    def __delete__(self, instance):
        print("delete")


class Person(object):
    age = Age()
    def __init__(self):
        self.age = 10

p = Person()

p.age = 10
print(p.age)

print(p.__dict__)

>>>> 打印結果
set
set
get
None
{}

  • 實例屬性 > 非資料描述器 測試
class Age(object):
    def __get__(self, instance, owner):
        print("get")


class Person(object):
    age = Age()
    def __init__(self):
        self.age = 10

p = Person()

p.age = 10
print(p.age)


print(p.__dict__)

>>>> 打印結果

10
{'age': 10}

5 描述器-值的存儲問題

  • 描述器Age是共享的
class Age:
    def __get__(self, instance, owner):
        print("get", self, instance, owner)
        if "v" in instance.__dict__:
            return instance.v

    def __set__(self, instance, value):
        print("set", self, instance, value)
        instance.v = value

    def __delete__(self, instance):
        print("delete", self, instance)
        del instance.v


class Person:
    age = Age()


p1 = Person()
print(p1.age)

p2 = Person()
print(p2.age)


>>>> 打印結果

get <__main__.Age object at 0x107e049e8> <__main__.Person object at 0x107e04a20> <class '__main__.Person'>
get <__main__.Age object at 0x107e049e8> <__main__.Person object at 0x107e04a58> <class '__main__.Person'>

# 只有 instance 的值是對應 Person 實例布朦,而 Age 描述器則是同一個
  • 所以應該把值存放到對應的類實例中
class Age:
    def __get__(self, instance, owner):
        print("get", self, instance, owner)
        if "v" in instance.__dict__:
            return instance.v

    def __set__(self, instance, value):
        print("set", self, instance, value)
        instance.v = value

    def __delete__(self, instance):
        print("delete", self, instance)
        del instance.v


class Person:
    age = Age()


p1 = Person()
p1.age = 10
print(p1.age)

p2 = Person()
p2.age = 19
print(p2.age)

>>>> 打印結果

set <__main__.Age object at 0x10457b9e8> <__main__.Person object at 0x10457ba20> 10
get <__main__.Age object at 0x10457b9e8> <__main__.Person object at 0x10457ba20> <class '__main__.Person'>
10
set <__main__.Age object at 0x10457b9e8> <__main__.Person object at 0x10457ba58> 19
get <__main__.Age object at 0x10457b9e8> <__main__.Person object at 0x10457ba58> <class '__main__.Person'>
19

  • 有些場景也可以共享 描述器哦,如你想保存最新被修改的值時昼窗,就應該將值綁定到共享的描述器中 self.v = value是趴,那么以后這個值就是一個共享值,哪個實例修改后膏秫,其他實例獲取到的就是被新修改的值

問題

  1. 解析:描述器的定義方式為類屬性形式右遭,但我們操作使用為什么都是通過對象來調(diào)用做盅?
  2. 類屬性是被各個對象共享的,那么有如何保證不同的對象可以操作到這個共享屬性的不同值窘哈?
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末吹榴,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子滚婉,更是在濱河造成了極大的恐慌图筹,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,029評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件让腹,死亡現(xiàn)場離奇詭異远剩,居然都是意外死亡,警方通過查閱死者的電腦和手機骇窍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,395評論 3 385
  • 文/潘曉璐 我一進店門瓜晤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人腹纳,你說我怎么就攤上這事痢掠。” “怎么了嘲恍?”我有些...
    開封第一講書人閱讀 157,570評論 0 348
  • 文/不壞的土叔 我叫張陵足画,是天一觀的道長。 經(jīng)常有香客問我佃牛,道長淹辞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,535評論 1 284
  • 正文 為了忘掉前任俘侠,我火速辦了婚禮象缀,結果婚禮上,老公的妹妹穿的比我還像新娘爷速。我一直安慰自己攻冷,他們只是感情好,可當我...
    茶點故事閱讀 65,650評論 6 386
  • 文/花漫 我一把揭開白布遍希。 她就那樣靜靜地躺著,像睡著了一般里烦。 火紅的嫁衣襯著肌膚如雪凿蒜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,850評論 1 290
  • 那天胁黑,我揣著相機與錄音废封,去河邊找鬼。 笑死丧蘸,一個胖子當著我的面吹牛漂洋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 39,006評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼刽漂,長吁一口氣:“原來是場噩夢啊……” “哼演训!你這毒婦竟也來了?” 一聲冷哼從身側響起贝咙,我...
    開封第一講書人閱讀 37,747評論 0 268
  • 序言:老撾萬榮一對情侶失蹤样悟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后庭猩,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體窟她,經(jīng)...
    沈念sama閱讀 44,207評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,536評論 2 327
  • 正文 我和宋清朗相戀三年蔼水,在試婚紗的時候發(fā)現(xiàn)自己被綠了震糖。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,683評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡趴腋,死狀恐怖吊说,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情于样,我是刑警寧澤疏叨,帶...
    沈念sama閱讀 34,342評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站穿剖,受9級特大地震影響蚤蔓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜糊余,卻給世界環(huán)境...
    茶點故事閱讀 39,964評論 3 315
  • 文/蒙蒙 一秀又、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧贬芥,春花似錦吐辙、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,772評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至威沫,卻和暖如春贤惯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背棒掠。 一陣腳步聲響...
    開封第一講書人閱讀 32,004評論 1 266
  • 我被黑心中介騙來泰國打工孵构, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人烟很。 一個月前我還...
    沈念sama閱讀 46,401評論 2 360
  • 正文 我出身青樓颈墅,卻偏偏與公主長得像蜡镶,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子恤筛,可洞房花燭夜當晚...
    茶點故事閱讀 43,566評論 2 349

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