詳解Python中type與object的恩怨糾葛
在學習 Python 的時候,你肯定聽過這么一句話:Python 中一切皆對象。沒錯,在 Python 世界里,一切都是對象。整數(shù)是一個對象、字符串是一個對象、字典是一個對象,甚至 int, str, list 等等,再加上我們使用 class 關(guān)鍵字自定義的類,它們也是對象。
像 int, str, list 等基本類型,以及我們自定義的類,由于它們可以表示類型,因此我們稱之為類型對象;類型對象實例化得到的對象,我們稱之為實例對象。但不管是哪種對象,它們都屬于對象。
因此 Python 將面向?qū)ο罄砟钬瀼氐姆浅氐?,面向?qū)ο笾械念惡蛯ο笤?Python 中都是通過對象實現(xiàn)的。
在面向?qū)ο罄碚撝?,存在著類和對象兩個概念,像 int、dict、tuple、以及使用 class 關(guān)鍵字自定義的類型對象實現(xiàn)了面向?qū)ο罄碚撝蓄惖母拍?,?123、(1, 2, 3),"xxx" 等等這些實例對象則實現(xiàn)了面向?qū)ο罄碚撝袑ο蟮母拍?。但?Python 里面,面向?qū)ο蟮念惡蛯ο蠖际峭ㄟ^對象實現(xiàn)的。
我們舉個例子:
#?dict 是一個類,因此它屬于類型對象 #?類型對象實例化得到的對象屬于實例對象 print(dict) """ <class?'dict'> """ print(dict(a=1,?b=2)) """ {'a':?1,?'b':?2} """
因此可以用一張圖來描述面向?qū)ο笤赑ython中的體現(xiàn):
而如果想查看一個對象的類型,可以使用 type,或者通過對象的 __class__ 屬性。
numbers?=?[1,?2,?3] #?查看類型 print(type(numbers)) """ <class?'list'> """ print(numbers.__class__) """ <class?'list'> """
如果想判斷一個對象是不是指定類型的實例對象,可以使用 isinstance。
numbers?=?[1,?2,?3] #?判斷是不是指定類型的實例對象 print(isinstance(numbers,?list)) """ True """
但是問題來了,按照面向?qū)ο蟮睦碚搧碚f,對象是由類實例化得到的,這在 Python 中也是適用的。既然是對象,那么就必定有一個類來實例化它,換句話說對象一定要有類型。
至于一個對象的類型是什么,就看這個對象是被誰實例化的,被誰實例化那么類型就是誰,比如列表的類型是 list,字典的類型是 dict 等等。
而我們說 Python 中一切皆對象,所以像 int, str, tuple 這些內(nèi)置的類對象也是具有相應的類型的,那么它們的類型又是誰呢?
我們使用 type 查看一下。
>>>?type(int) <class?'type'> >>>?type(str) <class?'type'> >>>?type(dict) <class?'type'> >>>?type(type) <class?'type'>
我們看到類型對象的類型,無一例外都是 type。而 type 我們也稱其為元類,表示類型對象的類型。至于 type 本身,它的類型還是 type,所以它連自己都沒放過,把自己都變成自己的對象了。
因此在 Python 中,你能看到的任何對象都是有類型的,我們可以使用 type 查看,也可以獲取該對象的 __class__ 屬性查看。所以:實例對象、類型對象、元類,Python 中任何一個對象都逃不過這三種身份。
到這里可能有人會發(fā)現(xiàn)一個有意思的點,我們說 int 是一個類對象,這顯然是沒有問題的。因為站在整數(shù)(比如 123)的角度上,int 是一個不折不扣的類對象;但如果站在 type 的角度上呢?顯然我們又可以將 int 理解為實例對象,因此 class 具有二象性。
至于 type 也是同理,雖然它是元類,但本質(zhì)上也是一個類對象。
注:不僅 type 是元類,那些繼承了 type 的類也可以叫做元類。
這些概念上的東西讀起來可能會有一點繞,但如果實際動手敲一敲代碼的話,還是很好理解的。
然后 Python 中還有一個關(guān)鍵的類型(對象),叫做 object,它是所有類型對象的基類。不管是什么類,內(nèi)置的類也好,我們自定義的類也罷,它們都繼承自 object。因此 object 是所有類型對象的基類、或者說父類。
那如果我們想獲取一個類都繼承了哪些基類,該怎么做呢?方式有三種:
class?A:?pass class?B:?pass class?C(A):?pass class?D(B,?C):?pass #?首先?D?繼承自?B?和?C,?C?又繼承?A #?我們現(xiàn)在要來查看?D?繼承的父類 #?方法一:?使用?__base__ print(D.__base__)?? """ <class?'__main__.B'> """ #?方法二:?使用?__bases__ print(D.__bases__)?? """ (<class?'__main__.B'>,?<class?'__main__.C'>) """ #?方法三:?使用?__mro__ print(D.__mro__) """ (<class?'__main__.D'>,?<class?'__main__.B'>,? ?<class?'__main__.C'>,?<class?'__main__.A'>,? ?<class?'object'>) """
- __base__:如果繼承了多個類,那么只顯示繼承的第一個類,沒有顯式繼承則返回 <class 'object'>
- __bases__:返回一個元組,會顯示所有直接繼承的父類,沒有顯式繼承, 則返回 (<class 'object'>,)
- __mro__: mro(Method Resolution Order)表示方法查找順序,會從自身出發(fā),找到最頂層的父類。因此返回自身、繼承的基類、以及基類繼承的基類, 一直找到 object
而如果想查看某個類型是不是另一個類型的子類,可以通過 issubclass。
print(issubclass(str,?object)) """ True """
因此,到目前為止,關(guān)于 type 和 object,我們可以得出以下兩個結(jié)論:
- type站在類型金字塔的最頂端, 任何一個對象按照類型追根溯源, 最終得到的都是type;
- object站在繼承金字塔的最頂端, 任何一個類型對象按照繼承關(guān)系追根溯源, 最終得到的都是object;
但要注意的是,我們說 type 的類型還是 type,但 object 的基類則不再是 object,而是 None。
print( ????type.__class__ )??#?<class?'type'> #?注:以下打印結(jié)果容易讓人產(chǎn)生誤解 #?它表達的含義是?object?的基類為空 #?而不是說?object?繼承?None print( ????object.__base__ )??#?None
但為什么 object 的基類是 None,而不是它自身呢?其實答案很簡單,Python 在查找屬性或方法的時候,自身如果沒有的話,會按照 __mro__ 指定的順序去基類中查找。所以繼承鏈一定會有一個終點,否則就會像沒有出口的遞歸一樣出現(xiàn)死循環(huán)了。
我們用一張圖將對象之間的關(guān)系總結(jié)一下:
- 實例對象的類型是類型對象,類型對象的類型是元類;
- 所有類型對象的基類都收斂于 object;
- 所有對象的類型都收斂于 type;
因此 Python 算是將一切皆對象的理念貫徹到了極致,也正因為如此,Python 才具有如此優(yōu)秀的動態(tài)特性。
但是還沒結(jié)束,我們再重新審視一下上面那張圖,會發(fā)現(xiàn)里面有兩個箭頭看起來非常的奇怪。object 的類型是 type,type 又繼承了 object。
>>>?type.__base__ <class?'object'> >>>?object.__class__ <class?'type'>
因為 type 是所有類的元類,而 object 是所有類的基類,這就說明 type 要繼承自 object,而 object 的類型是 type。很多人都會對這一點感到奇怪,這難道不是一個先有雞還是先有蛋的問題嗎?其實不是的,這兩個對象是共存的,它們之間的定義其實是互相依賴的。而具體是怎么一回事,我們一點一點分析。
首先在這里必須要澄清一個事實,類對象的類型是 type,這句話是沒有問題的;但如果說類對象都是由 type 創(chuàng)建的,就有些爭議了。因為 type 能夠創(chuàng)建的是自定義的類,而內(nèi)置的類在底層是預先定義好的。
#?int、tuple、dict?等內(nèi)置類型 #?在底層是預先定義好的,以全局變量的形式存在 #?我們直接就可以拿來用 print(int)??#?<class?'int'> print(tuple)??#?<class?'tuple'> #?但對于自定義的類,顯然就需要在運行時動態(tài)創(chuàng)建了 #?而創(chuàng)建這一過程,就交給?type?來做 class?Girl: ????pass
而 type 也只能對自定義類進行屬性上的增刪改,內(nèi)置的類則不行。
class?Girl: ????pass #?給類對象增加一個成員函數(shù) type.__setattr__( ????Girl, ????"info", ????lambda?self:?"name:?古明地覺,?age:?17" ) #?實例化之后就可以調(diào)用了 print(Girl().info())??#?name:?古明地覺,?age:?17 #?但內(nèi)置的類對象,type?是無法修改的 try: ????type.__setattr__(int,?"a",?"b") except?TypeError?as?e: ????print(e) """ can't?set?attributes?of?built-in/extension?type?'int' """
而 Python 所有內(nèi)置的類對象,在解釋器看來,都是同級別的。因為它們都是由同一個結(jié)構(gòu)體實例化得到的。
所有內(nèi)置的類對象都是 PyTypeObject 結(jié)構(gòu)體實例,只不過結(jié)構(gòu)體字段的值不同,得到的類也不同。所以元類 type 和普通的類對象,在解釋器看來都是等價的。
在解釋器看來,它們無一例外都是PyTypeObject結(jié)構(gòu)體實例。換句話說,它們都是基于這個結(jié)構(gòu)體創(chuàng)建出的全局變量罷了,這些變量代表的就是 Python 的類。
而每一個對象都有引用計數(shù)和類型,然后解釋器將這些類對象的類型都設置成了 type,我們以 object 為例:
我們看到它的類型被設置成了 type,所以結(jié)論很清晰了,雖然內(nèi)置類對象可以看做是 type 的實例對象,但它卻不是由 type 實例化得到的。所有內(nèi)置的類對象,在底層都是預定義好的,以靜態(tài)全局變量的形式出現(xiàn)。
至于 type 也是同理:
解釋器只是將 type 的類型設置成了它自身而已,所以內(nèi)置的類對象之間不存在誰創(chuàng)建誰。它們都是預定義好的,只是在定義的時候,將自身的類型設置成 type 而已,包括 type 本身。這樣一來,每一個對象都會具有一個類型,從而將面向?qū)ο罄砟钬瀼氐母訌氐住?/p>
print(int.__class__) print(tuple.__class__) print(set.__class__) print(type.__class__) """ <class?'type'> <class?'type'> <class?'type'> <class?'type'> """ print( ????type.__class__.__class__.__class__?is?type )??#?True print( ????type(type(type(type(type(type)))))?is?type )??#?True
現(xiàn)在 object 的類型是 type 我們已經(jīng)搞清楚是怎么一回事了,然后是基類的問題。PyTypeObject 結(jié)構(gòu)體內(nèi)部有一個 tp_base,它表示的就是類對象繼承的基類。
但令我們吃鯨的是,它的 tp_base 居然是個 0,如果為 0 的話則表示沒有這個屬性。不是說 type 的基類是 object 嗎?為啥 tp_base 是 0 呢。
事實上如果你去看 PyFloat_Type 以及其它類型的話,會發(fā)現(xiàn)它們內(nèi)部的 tp_base 也是 0。為 0 的原因就在于我們目前看到的類型對象是一個半成品,因為 Python 的動態(tài)性,顯然不可能在定義的時候就將所有成員屬性都設置好、然后解釋器一啟動就得到我們平時使用的類型對象。
目前看到的類型對象是一個半成品,有一部分成員屬性是在解釋器啟動之后再動態(tài)完善的,而這個完善的過程被稱為類型對象的初始化,它由函數(shù) PyType_Ready 負責。
首先代碼中的 type 只是一個普通的參數(shù),當解釋器發(fā)現(xiàn)一個類對象還沒有初始化時,會將其作為參數(shù)傳遞進來,進行初始化。base 則顯然是它的基類,然后如果基類為空,并且該類不是 object 的話,那么就將它的基類設置成 object。所以 Python3 中,所有的類默認都繼承 object,當然除了 object 本身。
因此到目前為止,type 和 object 之間的恩怨糾葛算是真相大白了,總結(jié)一下:
1)和自定義類不同,內(nèi)置的類不是由 type 實例化得到的,它們都是在底層預先定義好的,不存在誰創(chuàng)建誰。只是內(nèi)置的類在定義的時候,它們的類型也都被設置成了 type。這樣不管是內(nèi)置的類,還是自定義類,在調(diào)用時都會執(zhí)行 type 的 __call__ 方法,從而讓它們的行為是一致的。
2)雖然內(nèi)置的類在底層預定義好了,但還有一些瑕疵,因為有一部分邏輯無法以源碼的形式體現(xiàn),只能在解釋器啟動的時候再動態(tài)完善。而這個完善的過程,便包含了基類的填充,會將基類設置成 object。
所以 type 和 object 是同時出現(xiàn)的,它們的存在需要依賴彼此。首先這兩者會以不完全體的形式定義在源碼中,并且在定義的時候?qū)?object 的類型設置成 type;然后當解釋器啟動的時候,再經(jīng)過動態(tài)完善,進化成完全體,而進化的過程中會將 type 的基類設置成 object。
所以 object 的類型是 type,type 繼承 object 就是這么來的。
以上就是詳解Python中type與object的恩怨糾葛的詳細內(nèi)容,更多關(guān)于Python type object的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python+PyQt5實現(xiàn)數(shù)據(jù)庫表格動態(tài)增刪改
這篇文章主要為大家介紹如何利用Python中的PyQt5模塊實現(xiàn)對數(shù)據(jù)庫表格的動態(tài)增刪改,文中的示例代碼講解詳細,感興趣的小伙伴可以了解一下2022-03-03Ubuntu 20.04安裝Pycharm2020.2及鎖定到任務欄的問題(小白級操作)
這篇文章主要介紹了Ubuntu 20.04安裝Pycharm2020.2及鎖定到任務欄的問題,本教程給大家講解的很詳細,非常適合小白級操作,需要的朋友可以參考下2020-10-10Python實現(xiàn)實時數(shù)據(jù)采集新型冠狀病毒數(shù)據(jù)實例
在本篇文章里小編給大家整理了關(guān)于Python實現(xiàn)實時數(shù)據(jù)采集新型冠狀病毒數(shù)據(jù)實例內(nèi)容,有需要的朋友們可以學習參考下。2020-02-02