您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“Python的對(duì)象模型是什么”的有關(guān)知識(shí),在實(shí)際案例的操作過程中,不少人都會(huì)遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
在面向?qū)ο蟮睦碚撝校袃蓚€(gè)核心的概念:類和實(shí)例。類可以看成是一個(gè)模板,實(shí)例就是根據(jù)這個(gè)模板創(chuàng)建出來的對(duì)象。但在 Python 里面,類和實(shí)例都是對(duì)象,也就是所謂的類對(duì)象(或者類型對(duì)象)和實(shí)例對(duì)象。
為了避免后續(xù)出現(xiàn)歧義,我們這里把對(duì)象分為三種:
內(nèi)置類對(duì)象:比如 int、str、list、type、object 等等;
自定義類對(duì)象:通過 class 關(guān)鍵字定義的類,當(dāng)然我們也會(huì)把它和上面的內(nèi)置類對(duì)象統(tǒng)稱為類對(duì)象(或者類型對(duì)象);
實(shí)例對(duì)象:由類對(duì)象(內(nèi)置類對(duì)象或自定義類對(duì)象)創(chuàng)建的實(shí)例;
而對(duì)象之間存在以下兩種關(guān)系:
is-kind-of:對(duì)應(yīng)面向?qū)ο罄碚撝凶宇惡透割愔g的關(guān)系;
is-instance-of:對(duì)應(yīng)面向?qū)ο罄碚撝袑?shí)例對(duì)象和類對(duì)象之間的關(guān)系;
我們舉例說明:
class Girl(object): def say(self): return "古明地覺" girl = Girl() print(girl.say()) # 古明地覺
這段代碼便包含了上面的三種對(duì)象:object(內(nèi)置類對(duì)象),Girl(自定義類對(duì)象),girl(實(shí)例對(duì)象)。
顯然 Girl 和 object 之間是 is-kind-of 關(guān)系,即 Girl 是 object 的子類。值得一提的是,Python3 里面所有的類(除 object)都是默認(rèn)繼承自 object,即便我們這里不顯式繼承 object,也會(huì)默認(rèn)繼承的,但為了說明,我們就寫上了。
除了 Girl 是 object 的子類,我們還能看出 girl 和 Girl 之間存在 is-instance-of 關(guān)系,即 girl 是 Girl 的實(shí)例。當(dāng)然如果再進(jìn)一步的話,girl 和 object 之間也存在 is-instance-of 關(guān)系,girl 也是 object 的實(shí)例。
class Girl(object): pass girl = Girl() print(issubclass(Girl, object)) # True print(type(girl)) # <class '__main__.Girl'> print(isinstance(girl, Girl)) # True print(isinstance(girl, object)) # True
girl 是 Girl 這個(gè)類實(shí)例化得到的,所以 type(girl) 得到的是類對(duì)象 Girl。但 girl 也是 object 的實(shí)例對(duì)象,因?yàn)?Girl 繼承了 object。至于這其中的原理,我們會(huì)慢慢介紹。
Python 也提供了一些手段可以探測(cè)這些關(guān)系,除了上面的 type 之外,還可以使用對(duì)象的 __class__ 屬性探測(cè)一個(gè)對(duì)象和其它的哪些對(duì)象之間存在 is-instance-of 關(guān)系。
而通過對(duì)象的 __bases__ 屬性則可以探測(cè)一個(gè)對(duì)象和其它的哪些對(duì)象之間存在著 is-kind-of 關(guān)系。此外 Python 還提供了兩個(gè)函數(shù) issubclass 和 isinstance 來驗(yàn)證兩個(gè)對(duì)象之間是否存在著我們期望的關(guān)系。
class Girl(object): pass girl = Girl() print(girl.__class__) # <class '__main__.Girl'> print(Girl.__class__) # <class 'type'> # __class__是查看自己的類型是什么,也就是生成自己的類 # 而在介紹 Python 對(duì)象的時(shí)候,我們就看到了 # 任何一個(gè)對(duì)象都至少具備兩個(gè)東西: 一個(gè)是引用計(jì)數(shù)、一個(gè)是類型 # 所以 __class__ 是所有對(duì)象都具備的 # __base__只顯示直接繼承的第一個(gè)類 print(Girl.__base__) # <class 'object'> # __bases__ 會(huì)顯示直接繼承的所有類,以元組的形式 print(Girl.__bases__) # (<class 'object'>,)
我們畫一張圖總結(jié)一下:
另外需要注意里面的 type 和 object:
type 和 object 存在 is-kind-of 關(guān)系,因?yàn)?type 是 object 的子類;
object 和 type 存在 is-instance-of 關(guān)系,因?yàn)?object 是 type 的實(shí)例對(duì)象;
可能有人會(huì)好奇為什么會(huì)是這樣,而關(guān)于這一點(diǎn),我在 type 與 object 的恩怨糾葛這篇文章講得很詳細(xì)了,感興趣可以點(diǎn)擊閱讀一下。
簡(jiǎn)單來說就是,type 在底層對(duì)應(yīng)的結(jié)構(gòu)體為 PyType_Type、object 在底層對(duì)應(yīng)的結(jié)構(gòu)體為 PyBaseObject_Type。而在創(chuàng)建 object 的時(shí)候,將內(nèi)部的 ob_type 設(shè)置成了&PyType_Type;在創(chuàng)建type的時(shí)候,將內(nèi)部的 tp_base 設(shè)置成了&PyBaseObject_Type。
因此這兩者的定義是彼此依賴的,兩者是同時(shí)出現(xiàn)的,我們后面還會(huì)看到。
另外 type 的類型就是 type 本身,所以:
實(shí)例對(duì)象的類型是類型對(duì)象,類型對(duì)象的類型是元類;
所有類型對(duì)象的基類都收斂于 object;
所有對(duì)象的類型都收斂于 type;
因此 Python 算是將一切皆對(duì)象的理念貫徹到了極致,也正因?yàn)槿绱耍琍ython 才具有如此優(yōu)秀的動(dòng)態(tài)特性。
但還沒有結(jié)束,我們看一下類對(duì)象 Girl 的行為,首先它支持屬性設(shè)置:
class Girl(object): pass print(hasattr(Girl, "name")) # False Girl.name = "古明地覺" print(hasattr(Girl, "name")) # True print(Girl.name) # 古明地覺
一個(gè)類都已經(jīng)定義完了,我們后續(xù)還可以進(jìn)行屬性添加,這在其它的靜態(tài)語言中是不可能做到的。那么Python是如何做到的呢?我們說能夠?qū)傩赃M(jìn)行動(dòng)態(tài)添加,你會(huì)想到什么?是不是字典呢?
正如 global 名字空間一樣,我們猜測(cè)類應(yīng)該也有自己的屬性字典,往類里面設(shè)置屬性的時(shí)候,等價(jià)于向字典中添加鍵值對(duì),同理其它操作也與之類似。
class Girl(object): pass print(Girl.__dict__.get("name", "不存在")) # 不存在 Girl.name = "古明地覺" print(Girl.__dict__.get("name")) # 古明地覺
和操作全局變量是類似的,但是有一點(diǎn)需要注意:我們不能直接通過類的屬性字典來設(shè)置屬性。
try: Girl.__dict__["name"] = "古明地覺" except Exception as e: print(e) # 'mappingproxy' object does not support item assignment
雖然叫屬性字典,但其實(shí)是 mappingproxy 對(duì)象,該對(duì)象本質(zhì)上就是對(duì)字典進(jìn)行了一層封裝,在字典的基礎(chǔ)上移除了增刪改操作,也就是只保留了查詢功能。如果我們想給類增加屬性,可以采用直接賦值的方式,或者調(diào)用 setattr 函數(shù)也是可以的。
但在介紹如何篡改虛擬機(jī)的時(shí)候,我們提到過一個(gè)騷操作,可以通過 gc 模塊拿到 mappingproxy 對(duì)象里的字典。
import gc class Girl(object): pass gc.get_referents(Girl.__dict__)[0]["name"] = "古明地覺" print(Girl.name) # 古明地覺
并且這種做法除了適用于自定義類對(duì)象,還適用于內(nèi)置類對(duì)象。但是工作中不要這么做,知道有這么個(gè)操作就行。
除了設(shè)置屬性之外,我們還可以設(shè)置函數(shù)。
class Girl(object): pass Girl.info = lambda name: f"我是{name}" print(Girl.info("古明地覺")) # 我是古明地覺 # 如果實(shí)例調(diào)用的話,會(huì)和我們想象的不太一樣 # 因?yàn)閷?shí)例調(diào)用的話會(huì)將函數(shù)包裝成方法 try: Girl().info("古明地覺") except TypeError as e: print(e) """ <lambda>() takes 1 positional argument but 2 were given """ # 實(shí)例在調(diào)用的時(shí)候會(huì)將自身也作為參數(shù)傳進(jìn)去 # 所以第一個(gè)參數(shù) name 實(shí)際上接收的是 Girl 的實(shí)例對(duì)象 # 只不過第一個(gè)參數(shù)按照規(guī)范來講應(yīng)該叫做self # 但即便你起別的名字也是無所謂的 print(Girl().info()) """ 我是<__main__.Girl object at 0x000001920BB88760> """
所以我們可以有兩種做法:
# 將其包裝成一個(gè)靜態(tài)方法 # 這樣類和實(shí)例都可以調(diào)用 Girl.info = staticmethod(lambda name: f"我是{name}") print(Girl.info("古明地覺")) # 我是古明地覺 print(Girl().info("古明地覺")) # 我是古明地覺 # 如果是給實(shí)例用的,那么帶上一個(gè) self 參數(shù)即可 Girl.info = lambda self, name: f"我是{name}" print(Girl().info("古明地覺")) # 我是古明地覺
此外我們還可以通過 type 來動(dòng)態(tài)地往類里面進(jìn)行屬性的增加、修改和刪除。
class Girl(object): def say(self): pass print(hasattr(Girl, "say")) # True # delattr(Girl, "say") 與之等價(jià) type.__delattr__(Girl, "say") print(hasattr(Girl, "say")) # False # 我們?cè)O(shè)置一個(gè)屬性吧 # 等價(jià)于 Girl.name = "古明地覺" setattr(Girl, "name", "古明地覺") print(Girl.name) # 古明地覺
事實(shí)上調(diào)用 getattr、setattr、delattr 等價(jià)于調(diào)用其類型對(duì)象的__getattr__、__setattr__、__delattr__。
所以,一個(gè)對(duì)象支持哪些行為,取決于其類型對(duì)象定義了哪些操作。并且通過對(duì)象的類型對(duì)象,可以動(dòng)態(tài)地給該對(duì)象進(jìn)行屬性的設(shè)置。Python 所有類型對(duì)象的類型對(duì)象都是 type,通過 type 我們便可以控制類的生成過程,即便類已經(jīng)創(chuàng)建完畢了,也依舊可以進(jìn)行屬性設(shè)置。
但是注意:type 可以操作的類只能是通過 class 定義的動(dòng)態(tài)類,而像 int、list、dict 等靜態(tài)類,它們是在源碼中靜態(tài)定義好的,只不過類型設(shè)置成了 type。一言以蔽之,type 雖然是所有類對(duì)象的類對(duì)象,但 type 只能對(duì)動(dòng)態(tài)類進(jìn)行屬性上的修改,不能修改靜態(tài)類。
try: int.name = "古明地覺" except Exception as e: print(e) """ can't set attributes of built-in/extension type 'int' """ try: setattr(int, "ping", "pong") except Exception as e: print(e) """ can't set attributes of built-in/extension type 'int' """
通過報(bào)錯(cuò)信息可以看到,不可以設(shè)置內(nèi)置類和擴(kuò)展類的屬性,因?yàn)閮?nèi)置類在解釋器啟動(dòng)之后,就已經(jīng)初始化好了。至于擴(kuò)展類就是我們使用 Python/C API 編寫的擴(kuò)展模塊中的類,它和內(nèi)置類是等價(jià)的。
因此內(nèi)置類和使用 class 定義的類本質(zhì)上是一樣的,都是 PyTypeObject 對(duì)象,它們的類型在 Python 里面都是 type。但區(qū)別在于內(nèi)置類在底層是靜態(tài)初始化的,我們不能進(jìn)行屬性的動(dòng)態(tài)設(shè)置(通過 gc 模塊實(shí)現(xiàn)除外)。
但是為什么不可以對(duì)內(nèi)置類和擴(kuò)展類進(jìn)行屬性設(shè)置呢?首先我們要知道 Python 的動(dòng)態(tài)特性是虛擬機(jī)賜予的,而虛擬機(jī)的工作就是將 PyCodeObject 對(duì)象翻譯成 C 的代碼進(jìn)行執(zhí)行,所以 Python 的動(dòng)態(tài)特性就是在這一步發(fā)生的。
而內(nèi)置類在解釋器啟動(dòng)之后就已經(jīng)靜態(tài)初始化好了,直接指向 C 一級(jí)的數(shù)據(jù)結(jié)構(gòu),同理擴(kuò)展類也是如此。它們相當(dāng)于繞過了解釋執(zhí)行這一步,所以它們的屬性不可以動(dòng)態(tài)添加。
不光內(nèi)置的類本身,還有它的實(shí)例對(duì)象也是如此。
a = 123 print(hasattr(a, "__dict__")) # False
我們看到它連自己的屬性字典都沒有,因?yàn)閮?nèi)置類對(duì)象的實(shí)例對(duì)象,內(nèi)部有哪些屬性,解釋器記得清清楚楚。它們?cè)诘讓佣家呀?jīng)寫死了,并且不允許修改,因此虛擬機(jī)完全沒有必要為其實(shí)現(xiàn)屬性字典(節(jié)省了內(nèi)存占用)。
“Python的對(duì)象模型是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。