日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > python >内容正文

python

Python 中的属性访问与描述符

發(fā)布時間:2025/3/20 python 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Python 中的属性访问与描述符 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

在Python中,對于一個對象的屬性訪問,我們一般采用的是點(diǎn)(.)屬性運(yùn)算符進(jìn)行操作。例如,有一個類實(shí)例對象foo,它有一個name屬性,那便可以使用foo.name對此屬性進(jìn)行訪問。一般而言,點(diǎn)(.)屬性運(yùn)算符比較直觀,也是我們經(jīng)常碰到的一種屬性訪問方式。然而,在點(diǎn)(.)屬性運(yùn)算符的背后卻是別有洞天,值得我們對對象的屬性訪問進(jìn)行探討。

在進(jìn)行對象屬性訪問的分析之前,我們需要先了解一下對象怎么表示其屬性。為了便于說明,本文以新式類為例。有關(guān)新式類和舊式類的區(qū)別,大家可以查看Python官方文檔。

對象的屬性

Python中,“一切皆對象”。我們可以給對象設(shè)置各種屬性。先來看一個簡單的例子:

class Animal(object):run = True class Dog(Animal):fly = Falsedef __init__(self, age):self.age = agedef sound(self):return "wang wang~"

上面的例子中,我們定義了兩個類。類Animal定義了一個屬性run;類Dog繼承自Animal,定義了一個屬性fly和兩個函數(shù)。接下來,我們實(shí)例化一個對象。對象的屬性可以從特殊屬性__dict__中查看。

''' 遇到問題沒人解答?小編創(chuàng)建了一個Python學(xué)習(xí)交流QQ群:857662006 尋找有志同道合的小伙伴, 互幫互助,群里還有不錯的視頻學(xué)習(xí)教程和PDF電子書! ''' # 實(shí)例化一個對象dog >>> dog = Dog(1) # 查看dog對象的屬性 >>> dog.__dict__ {'age': 1} # 查看類Dog的屬性 >>> Dog.__dict__ dict_proxy({'__doc__': None,'__init__': <function __main__.__init__>,'__module__': '__main__','fly': False,'sound': <function __main__.sound>}) # 查看類Animal的屬性 >>> Animal.__dict__ dict_proxy({'__dict__': <attribute '__dict__' of 'Animal' objects>,'__doc__': None,'__module__': '__main__','__weakref__': <attribute '__weakref__' of 'Animal' objects>,'run': True})

由上面的例子可以看出:屬性在哪個對象上定義,便會出現(xiàn)在哪個對象的__dict__中。例如:

  • 類Animal定義了一個屬性run,那這個run屬性便只會出現(xiàn)在類Animal的__dict__中,而不會出現(xiàn)在其子類中。
  • 類Dog定義了一個屬性fly和兩個函數(shù),那這些屬性和方法便會出現(xiàn)在類Dog的__dict__中,同時它們也不會出現(xiàn)在實(shí)例的__dict__中。
  • 實(shí)例對象dog的__dict__中只出現(xiàn)了一個屬性age,這是在初始化實(shí)例對象的時候添加的,它沒有父類的屬性和方法。

由此可知:Python中對象的屬性具有 “層次性”,屬性在哪個對象上定義,便會出現(xiàn)在哪個對象的__dict__中。
在這里我們首先了解的是屬性值會存儲在對象的__dict__中,查找也會在對象的__dict__中進(jìn)行查找的。至于Python對象進(jìn)行屬性訪問時,會按照怎樣的規(guī)則來查找屬性值呢?這個問題在后文中進(jìn)行討論。

對象屬性訪問與特殊方法__getattribute__

正如前面所述,Python的屬性訪問方式很直觀,使用點(diǎn)屬性運(yùn)算符。在新式類中,對對象屬性的訪問,都會調(diào)用特殊方法__getattribute__。__getattribute__允許我們在訪問對象屬性時自定義訪問行為,但是使用它特別要小心無限遞歸的問題。

還是以上面的情景為例:

''' 遇到問題沒人解答?小編創(chuàng)建了一個Python學(xué)習(xí)交流QQ群:857662006 尋找有志同道合的小伙伴, 互幫互助,群里還有不錯的視頻學(xué)習(xí)教程和PDF電子書! ''' class Animal(object):run = True class Dog(Animal):fly = Falsedef __init__(self, age):self.age = age# 重寫__getattribute__。需要注意的是重寫的方法中不能# 使用對象的點(diǎn)運(yùn)算符訪問屬性,否則使用點(diǎn)運(yùn)算符訪問屬性時,# 會再次調(diào)用__getattribute__。這樣就會陷入無限遞歸。# 可以使用super()方法避免這個問題。def __getattribute__(self, key):print "calling __getattribute__\n"return super(Dog, self).__getattribute__(key)def sound(self):return "wang wang~"

上面的例子中我們重寫了__getattribute__方法。注意我們使用了super()方法來避免無限循環(huán)問題。下面我們實(shí)例化一個對象來說明訪問對象屬性時__getattribute__的特性。

# 實(shí)例化對象dog >>> dog = Dog(1) # 訪問dog對象的age屬性 >>> dog.age calling __getattribute__ 1 # 訪問dog對象的fly屬性 >>> dog.fly calling __getattribute__ False # 訪問dog對象的run屬性 >>> dog.run calling __getattribute__ True # 訪問dog對象的sound方法 >>> dog.sound calling __getattribute__ <bound method Dog.sound of <__main__.Dog object at 0x0000000005A90668>>

由上面的驗(yàn)證可知,__ getattribute__是實(shí)例對象查找屬性或方法的入口。實(shí)例對象訪問屬性或方法時都需要調(diào)用到__getattribute __ ,之后才會根據(jù)一定的規(guī)則在各個__dict__中查找相應(yīng)的屬性值或方法對象,若沒有找到則會調(diào)用__getattr__(后面會介紹到)。__getattribute__是Python中的一個內(nèi)置方法,關(guān)于其底層實(shí)現(xiàn)可以查看相關(guān)官方文檔,后面將要介紹的屬性訪問規(guī)則就是依賴于__getattribute__的。

對象屬性控制

在繼續(xù)介紹后面相關(guān)內(nèi)容之前,讓我們先來了解一下Python中和對象屬性控制相關(guān)的相關(guān)方法。

__ getattr__(self, name)__ getattr__可以用來在當(dāng)用戶試圖訪問一個根本不存在(或者暫時不存在)的屬性時,來定義類的行為。前面講到過,當(dāng)__getattribute__方法找不到屬性時,最終會調(diào)用__getattr__方法。它可以用于捕捉錯誤的以及靈活地處理AttributeError。只有當(dāng)試圖訪問不存在的屬性時它才會被調(diào)用。

__ setattr__(self, name, value)__ setattr__方法允許你自定義某個屬性的賦值行為,不管這個屬性存在與否,都可以對任意屬性的任何變化都定義自己的規(guī)則。關(guān)于__setattr__有兩點(diǎn)需要說明:第一,使用它時必須小心,不能寫成類似self.name = “Tom”這樣的形式,因?yàn)檫@樣的賦值語句會調(diào)用__setattr__方法,這樣會讓其陷入無限遞歸;第二,你必須區(qū)分 對象屬性 和 類屬性 這兩個概念。后面的例子中會對此進(jìn)行解釋。

__ delattr__(self, name)__delattr__用于處理刪除屬性時的行為。和__setattr__方法要注意無限遞歸的問題,重寫該方法時不要有類似del self.name的寫法。

還是以上面的例子進(jìn)行說明,不過在這里我們要重寫三個屬性控制方法。

''' 遇到問題沒人解答?小編創(chuàng)建了一個Python學(xué)習(xí)交流QQ群:857662006 尋找有志同道合的小伙伴, 互幫互助,群里還有不錯的視頻學(xué)習(xí)教程和PDF電子書! ''' class Animal(object):run = True class Dog(Animal):fly = Falsedef __init__(self, age):self.age = agedef __getattr__(self, name):print "calling __getattr__\n"if name == 'adult':return True if self.age >= 2 else Falseelse:raise AttributeErrordef __setattr__(self, name, value):print "calling __setattr__"super(Dog, self).__setattr__(name, value)def __delattr__(self, name):print "calling __delattr__"super(Dog, self).__delattr__(name)

以下進(jìn)行驗(yàn)證。首先是__getattr__:

# 創(chuàng)建實(shí)例對象dog >>> dog = Dog(1) calling __setattr__ # 檢查一下dog和Dog的__dict__ >>> dog.__dict__ {'age': 1} >>> Dog.__dict__ dict_proxy({'__delattr__': <function __main__.__delattr__>,'__doc__': None,'__getattr__': <function __main__.__getattr__>,'__init__': <function __main__.__init__>,'__module__': '__main__','__setattr__': <function __main__.__setattr__>,'fly': False}) # 獲取dog的age屬性 >>> dog.age 1 # 獲取dog的adult屬性。 # 由于__getattribute__沒有找到相應(yīng)的屬性,所以調(diào)用__getattr__。 >>> dog.adult calling __getattr__ False # 調(diào)用一個不存在的屬性name,__getattr__捕獲AttributeError錯誤 >>> dog.name calling __getattr__ Traceback (most recent call last):File "<stdin>", line 1, in <module>File "<stdin>", line 10, in __getattr__ AttributeError

可以看到,屬性訪問時,當(dāng)訪問一個不存在的屬性時觸發(fā)__getattr__,它會對訪問行為進(jìn)行控制。接下來是__setattr__:

''' 遇到問題沒人解答?小編創(chuàng)建了一個Python學(xué)習(xí)交流QQ群:857662006 尋找有志同道合的小伙伴, 互幫互助,群里還有不錯的視頻學(xué)習(xí)教程和PDF電子書! ''' # 給dog.age賦值,會調(diào)用__setattr__方法 >>> dog.age = 2 calling __setattr__ >>> dog.age 2 # 先調(diào)用dog.fly時會返回False,這時因?yàn)镈og類屬性中有fly屬性; # 之后再給dog.fly賦值,觸發(fā)__setattr__方法。 >>> dog.fly False >>> dog.fly = True calling __setattr__ # 再次查看dog.fly的值以及dog和Dog的__dict__; # 可以看出對dog對象進(jìn)行賦值,會在dog對象的__dict__中添加了一條對象屬性; # 然而,Dog類屬性沒有發(fā)生變化 # 注意:dog對象和Dog類中都有fly屬性,訪問時會選擇哪個呢? >>> dog.fly True >>> dog.__dict__ {'age': 2, 'fly': True} >>> Dog.__dict__ dict_proxy({'__delattr__': <function __main__.__delattr__>,'__doc__': None,'__getattr__': <function __main__.__getattr__>,'__init__': <function __main__.__init__>,'__module__': '__main__','__setattr__': <function __main__.__setattr__>,'fly': False})

實(shí)例對象的__setattr__方法可以定義屬性的賦值行為,不管屬性是否存在。當(dāng)屬性存在時,它會改變其值;當(dāng)屬性不存在時,它會添加一個對象屬性信息到對象的__dict__中,然而這并不改變類的屬性。從上面的例子可以看出來。

最后,看一下__delattr__:

''' 遇到問題沒人解答?小編創(chuàng)建了一個Python學(xué)習(xí)交流QQ群:857662006 尋找有志同道合的小伙伴, 互幫互助,群里還有不錯的視頻學(xué)習(xí)教程和PDF電子書! ''' # 由于上面的例子中我們?yōu)閐og設(shè)置了fly屬性,現(xiàn)在刪除它觸發(fā)__delattr__方法 >>> del dog.fly calling __delattr__ # 再次查看dog對象的__dict__,發(fā)現(xiàn)和fly屬性相關(guān)的信息被刪除 >>> dog.__dict__ {'age': 2}

描述符

描述符是Python 2.2 版本中引進(jìn)來的新概念。描述符一般用于實(shí)現(xiàn)對象系統(tǒng)的底層功能, 包括綁定和非綁定方法、類方法、靜態(tài)方法特特性等。關(guān)于描述符的概念,官方并沒有明確的定義,可以在網(wǎng)上查閱相關(guān)資料。這里我從自己的認(rèn)識談一些想法,如有不當(dāng)之處還請包涵。

在前面我們了解了對象屬性訪問和行為控制的一些特殊方法,例如__getattribute__、__ getattr__、__ setattr__、__ delattr__。以我的理解來看,這些方法應(yīng)當(dāng)具有屬性的”普適性”,可以用于屬性查找、設(shè)置、刪除的一般方法,也就是說所有的屬性都可以使用這些方法實(shí)現(xiàn)屬性的查找、設(shè)置、刪除等操作。但是,這并不能很好地實(shí)現(xiàn)對某個具體屬性的訪問控制行為。例如,上例中假如要實(shí)現(xiàn)dog.age屬性的類型設(shè)置(只能是整數(shù)),如果單單去修改__setattr__方法滿足它,那這個方法便有可能不能支持其他的屬性設(shè)置。

在類中設(shè)置屬性的控制行為不能很好地解決問題,Python給出的方案是:__ getattribute__、__ getattr__、__ setattr__、__ delattr__等方法用來實(shí)現(xiàn)屬性查找、設(shè)置、刪除的一般邏輯,而對屬性的控制行為就由屬性對象來控制。這里單獨(dú)抽離出來一個屬性對象,在屬性對象中定義這個屬性的查找、設(shè)置、刪除行為。這個屬性對象就是描述符。

描述符對象一般是作為其他類對象的屬性而存在。在其內(nèi)部定義了三個方法用來實(shí)現(xiàn)屬性對象的查找、設(shè)置、刪除行為。這三個方法分別是:

  • get(self, instance, owner):定義當(dāng)試圖取出描述符的值時的行為。
  • set(self, instance, value):定義當(dāng)描述符的值改變時的行為。
  • delete(self, instance):定義當(dāng)描述符的值被刪除時的行為。

其中:instance為把描述符對象作為屬性的對象實(shí)例;
owner為instance的類對象。

以下以官方的一個例子進(jìn)行說明:

''' 遇到問題沒人解答?小編創(chuàng)建了一個Python學(xué)習(xí)交流QQ群:857662006 尋找有志同道合的小伙伴, 互幫互助,群里還有不錯的視頻學(xué)習(xí)教程和PDF電子書! ''' class RevealAccess(object):def __init__(self, initval=None, name='var'):self.val = initvalself.name = namedef __get__(self, obj, objtype):print 'Retrieving', self.namereturn self.valdef __set__(self, obj, val):print 'Updating', self.nameself.val = val class MyClass(object):x = RevealAccess(10, 'var "x"')y = 5

以上定義了兩個類。其中RevealAccess類的實(shí)例是作為MyClass類屬性x的值存在的。而且RevealAccess類定義了__get__、__ set__方法,它是一個描述符對象。注意,描述符對象的__get__、__ set__方法中使用了諸如self.val和self.val = val等語句,這些語句會調(diào)用__getattribute__、__ setattr__等方法,這也說明了__getattribute__、__ setattr__等方法在控制訪問對象屬性上的一般性(一般性是指對于所有屬性它們的控制行為一致),以及__get__、__set__等方法在控制訪問對象屬性上的特殊性(特殊性是指它針對某個特定屬性可以定義不同的行為)。

以下進(jìn)行驗(yàn)證:

# 創(chuàng)建Myclass類的實(shí)例m >>> m = MyClass() # 查看m和MyClass的__dict__ >>> m.__dict__ {} >>> MyClass.__dict__ dict_proxy({'__dict__': <attribute '__dict__' of 'MyClass' objects>,'__doc__': None,'__module__': '__main__','__weakref__': <attribute '__weakref__' of 'MyClass' objects>,'x': <__main__.RevealAccess at 0x5130080>,'y': 5}) # 訪問m.x。會先觸發(fā)__getattribute__方法 # 由于x屬性的值是一個描述符,會觸發(fā)它的__get__方法 >>> m.x Retrieving var "x" 10 # 設(shè)置m.x的值。對描述符進(jìn)行賦值,會觸發(fā)它的__set__方法 # 在__set__方法中還會觸發(fā)__setattr__方法(self.val = val) >>> m.x = 20 Updating var "x" # 再次訪問m.x >>> m.x Retrieving var "x" 20 # 查看m和MyClass的__dict__,發(fā)現(xiàn)這與對描述符賦值之前一樣。 # 這一點(diǎn)與一般屬性的賦值不同,可參考上述的__setattr__方法。 # 之所以前后沒有發(fā)生變化,是因?yàn)樽兓w現(xiàn)在描述符對象上, # 而不是實(shí)例對象m和類MyClass上。 >>> m.__dict__ {} >>> MyClass.__dict__ dict_proxy({'__dict__': <attribute '__dict__' of 'MyClass' objects>,'__doc__': None,'__module__': '__main__','__weakref__': <attribute '__weakref__' of 'MyClass' objects>,'x': <__main__.RevealAccess at 0x5130080>,'y': 5})

上面的例子對描述符進(jìn)行了一定的解釋,不過對描述符還需要更進(jìn)一步的探討和分析,這個工作先留待以后繼續(xù)進(jìn)行。

最后,還需要注意一點(diǎn):描述符有數(shù)據(jù)描述符和非數(shù)據(jù)描述符之分。

  • 只要至少實(shí)現(xiàn)__get__、__ set__、__ delete__方法中的一個就可以認(rèn)為是描述符;
  • 只實(shí)現(xiàn)__get__方法的對象是非數(shù)據(jù)描述符,意味著在初始化之后它們只能被讀取;
  • 同時實(shí)現(xiàn)__get__和__set__的對象是數(shù)據(jù)描述符,意味著這種屬性是可讀寫的。

屬性訪問的優(yōu)先規(guī)則

在以上的討論中,我們一直回避著一個問題,那就是屬性訪問時的優(yōu)先規(guī)則。我們了解到,屬性一般都在__dict__中存儲,但是在訪問屬性時,在對象屬性、類屬型、基類屬性中以怎樣的規(guī)則來查詢屬性呢?以下對Python中屬性訪問的規(guī)則進(jìn)行分析。

由上述的分析可知,屬性訪問的入口點(diǎn)是__getattribute__方法。它的實(shí)現(xiàn)中定義了Python中屬性訪問的優(yōu)先規(guī)則。Python官方文檔中對__getattribute__的底層實(shí)現(xiàn)有相關(guān)的介紹,本文暫時只是討論屬性查找的規(guī)則,

Python屬性查找

查找屬性的第一步是搜索基類列表,即type(b).__ mro__,直到找到該屬性的第一個定義,并將該屬性的值賦值給descr;
判斷descr的類型。它的類型可分為數(shù)據(jù)描述符、非數(shù)據(jù)描述符、普通屬性、未找到等類型。若descr為數(shù)據(jù)描述符,則調(diào)用desc.__ get__(b, type(b)),并將結(jié)果返回,結(jié)束執(zhí)行。否則進(jìn)行下一步;
如果descr為非數(shù)據(jù)描述符、普通屬性、未找到等類型,則查找實(shí)例b的實(shí)例屬性,即b.__ dict__。如果找到,則將結(jié)果返回,結(jié)束執(zhí)行。否則進(jìn)行下一步;
如果在b.__ dict__未找到相關(guān)屬性,則重新回到descr值的判斷上。

  • 若descr為非數(shù)據(jù)描述符,則調(diào)用desc.__ get__(b, type(b)),并將結(jié)果返回,結(jié)束執(zhí)行;
  • 若descr為普通屬性,直接返回結(jié)果并結(jié)束執(zhí)行;
  • 若descr為空(未找到),則最終拋出 AttributeError 異常,結(jié)束查找。

總結(jié)

以上是生活随笔為你收集整理的Python 中的属性访问与描述符的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。