python中的type和object詳解
關于這篇博客
這篇博客主要描述Python的新風格對象(new-style objects),如下:
<type 'type'>和<type 'object'>分別是什么?
用戶自定義的類和實例之間是怎么聯(lián)系的?它們和內置類型又是怎么聯(lián)系的?
什么是元類(metaclasses)?
New-style表明這篇博客所說的內容只適用于版本為2.2及以上的python。
開始之前
最主要,是理解type和object的區(qū)別與聯(lián)系。我們平時用的最多的是Object,比如你定義一個類時,會繼承object:
In [6]: class Test(object):
...: pass
也就是說,object是所有類的超類。而type是什么呢?它是object的類型(也就是說object是type的實例),同時,object又是type的超類。
總之來說,object和type是雞生蛋,蛋生雞,誰先生誰,無法說。
你要明白這些,先要知道,python是面向對象的語言。在python里面,所有的東西都是對象的概念。而在面想對象的體系中,存在兩種關系:
父子關系(圖中以實線描述):這種關系存在于某個類(subclass)是另一個類(superclass)的特別版本之中。通常描述為“子類是一種父類”。.比如:蛇是一種爬行動物(Snake is a kind of reptile)。其中,蛇(snake)是子類,爬行動物(reptile)是父類。蛇擁有爬行動物的特征,同時,又擁有標志自己是一條蛇的特征。
類型實例關系(圖中以虛線描述):這種關系存在于兩個對象之中,其中一個對象(實例)是另一個對象(類型)的具體實現。我有一條寵物蛇叫Squasher,那么Squasher就是蛇的一個實例。英文描述為:"Squasher is an instance of snake".
[圖片上傳失敗...(image-5e7e19-1547088489988)]
用實線表示父子關系,是因為父與子的關系更加“貼實”。比如有人叫你列出有關蛇的詞,你可能會說爬行動物,但你不會說出Squasher....
這時候,有兩條很有用的規(guī)則:
Dashed Arrow Up Rule:If X is an instance of A, and A is a subclass of B, then X is an instance of B as well.翻譯過來應該是“虛線向上規(guī)則”:如果X是A的實例,同時A又是B的子類,那么,X也是B的實例。;
Dashed Arrow Down Rule:If B is an instance of M, and A is a subclass of B, then A is an instance of M as well.翻譯過來應該是“虛線向下規(guī)則”:如果B是M的實例,同時A是B的子類,那么,A也是M的實例。其實這條規(guī)則很少會用到,但卻和這篇博客要講的內容息息相關。我來略作分析,從“如果B是M的實例”這句話得出,B是
實例,“A是B的子類” --> B是一個(父)類。B是實例,同時又是一個類?怎么回事?看完這篇博客,你會知道答案的。
[圖片上傳失敗...(image-281b4a-1547088489988)]
在這里,我來解釋一下為什么叫"虛線向上規(guī)則",通過觀察上圖右邊,我們可以清晰地見到一個帶箭頭的虛線,從X端出發(fā),射向A端,此時,A端為箭頭端,虛線代表類型實例關系,所以A端是類型,即X是A的實例(換句話說A是X的類型),通過命令X.__class__我們可查看X的類型。再看,一條帶箭頭的實線從A端射向B端,B端是箭頭端,實現代表父子關系,所以B端是父類,即A是B的子類。這時候,我們通過將X端射向A端的虛線,向上抬,射向B端,就實現了表述X也是是B的實例的目的。也名副其實,虛線向上嘛。虛線向下規(guī)則也可以這樣推出來,我就不演示了。
總的來說,面向對象體系里,有兩種關系,一種是父子關系,通過父類與子類來描述,另一種是類型實例關系,通過類和實例來描述。而兩條規(guī)則,是將類之間,類與實例之間的關系聯(lián)系在一起。
到這里,可以進入主題了。
基本概念
對象內部:The Object Within
上面我們說了面向對象,那么對象(object)是什么呢?對象是python的重要核心之一:它是某個實體的抽象描述。對象擁有下面的特征:
身份(Identity):給定兩個名字,我可以肯定地說,要么它們是指向同一個對象,要么不是;
值(A value):這意味著對象包含一堆屬性。我們可以通過
objectname.attributename的方式操作屬性;類型(A type):每個對象都有一個確切地類型。例如,對象“2”的類型是
int;一個或多個“Bases”(One or more bases):不是所有對象都有Bases,但一些特殊的對象會有,比如:類。Bases類似于面向對象語言中的“基類”,“超類”。
如果你想知道一個對象在內存中的位置,你可以調用id(對象)來查看。在這里,我再次強調,在python中,一切都有對象的概念。數字2是對象,類型int也是對象...
type和Bases(如果它們存在)非常重要,因為它們定義了一個對象和另一個對象之間的關系。請記住,type和Bases本身也是對象,稍后會提到。
你也許會認為,對象有名字,但名字并不是對象的組成部分。對象的名字存在于這個對象的命名空間(namespace)之外或者是另一個對象的屬性。也就是說:名字和這個對象不是存儲在同一個“地方”
例子:測試一個整數對象
>>> two = 2 #==>(1)
>>> type(two) #==>(2)
<class 'int'>
>>> type(type(two)) #==>(3)
<class 'type'>
>>> type(two).__bases__ #==>(4)
(<class 'object'>,)
>>> dir(two) #==>(5)
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']
(1):我們在當前命名空間給數字2分配一個名字。并將2和“two”綁定在起。
(2):這個對象的類型是<class 'int'>。你會在其它地方見到類似<type 'int'>,int這樣的輸出,其實它們都是一個意思。
(3):額,<class 'int'>的類型是<class 'type'>.
(4):輸出“類int”的基類。
(5):列出整型這個對象所有的屬性。
可能會覺得有點亂,我稍微總結一下:數字2是類型int(一般來講,在python中“類”和“類型”是同一種東西)的一個實例。倒過來說,int是數字2的類型。元組(<class 'object'>,)是類型int的超類(或說父類)。一個類型的超類可能不止一個,所以用元組表示。
現在,我們引出第一條規(guī)則:
一切皆為對象
上面說的數字2,類型int,int的超類<class 'object'>都是對象。除此之外,你定義的函數,方法...都是對象。
一塊干凈的畫板
現在我們來建立python的對象系統(tǒng)。從一塊干凈的畫板開始...,畫板分為三部分,從左到右,分別代表類的類,類,類的實例。
[圖片上傳失敗...(image-f2fcf6-1547088489992)]
我們會在這個畫板中開啟我們的學習之旅...
關系(Relationships)
我們談及對象時,用兩種關系來連接各種對象,使得對象之間產生聯(lián)系:
父子關系( the subclass-superclass relationship);
類型實例關系( the type-instance relationship )。
在文章開頭已經詳細討論過這兩種關系了。
進入對象( Bring In The Objects)
第一個對象
我們測試兩個對象:object和type:
例子1:
>>> object #===>(1)
<class 'object'>
>>> type #===>(2)
<class 'type'>
>>> type(object) #===>(3)
<class 'type'>
>>> object.__class__ #===>(4)
<class 'type'>
>>> object.__bases__ #===>(5)
()
>>> type.__class__ #===>(6)
<class 'type'>
>>> type.__bases__ #===>(7)
(<class 'object'>,)
(1),(2):python中的兩個源對象的名字。我們先前說過type()是用來獲對象的類型的。事實上,它既是一個對象,也是獲取其它對象的類型的方法。
(3),(4):查看object的類型??吹?strong>object是type的實例,我們另外也用.__class__來核實它和type()的輸出是一樣的。
(5):object沒有超類,因為它本身就是所有對象的超類。
(6),(7):分別輸出type的類型和超類。即,object是type的超類。type的類型是它自己
我們把例子1獲取的信息描述在畫板上:
Figure 2.1. 雞和雞蛋
[圖片上傳失敗...(image-f89e4c-1547088489992)]
object和type是python中的兩個源對象,當我們嘗試介紹它們是就會陷入“先有雞還是現有蛋”難題,到底先介紹誰?事實上,它們是互相依賴對方來定義,所以它們不能分開而論。
繼續(xù)我們的python實驗:
>>> isinstance(object,object) #===>(1)
True
>>> isinstance(type, object) #===>(2)
True
(1):發(fā)生了什么?其實這里利用了虛線向上規(guī)則,type是object的子類,type的實例自然也是object的實例。object是type的實例啊。
(2):這里我參考的英文文檔解釋是:同時應用虛線向上和虛線向下規(guī)則。但我看得一臉懵逼。因為我覺的這里和(1)一樣?。簍ype是object的子類,type的實例自然也是object的實例。type也是type的實例啊。
如果你認為上面的解釋很混亂,不用例會它。不影響你理解這篇文章的主要目的。
新概念: type objects
type和object都屬于type objects。type objects翻譯過來就是類型對象了。類型對象都的特征:
它們用于表示程序中的抽象數據類型。例如,我們定義的一個類
User會代表系統(tǒng)中所有的用戶。int會代表系統(tǒng)中所有數字。它們能被繼承。這意味著你可以利用存在的類型對象創(chuàng)造出新的類型對象。已經存在的類型對象是新的類型對象的超類。
它們能被實例化。這意味著你可以利用已經存在的類型對象創(chuàng)造出新的實例對象。前者是后者的
type。類型對象的類型是
type它們有時會被成為類型有時會被稱為類。
你沒有看錯。在新版本的python中類和類型已經是同一樣東西了。由一個很明顯的地方就可以看出來。__class__和type()的輸出是一樣的。
在舊版本的python中,類是特指用class語句創(chuàng)造出來的東西。而內置類型例如int一般不會被認為是類,而是被認為是類型。但在新版本中它們是同一樣東西了。我覺得有必要為這個改變定義一條規(guī)則:
類是類型,類型也是類(Class is Type is Class)
在>=2.3版本的python中,類和類型是同一樣東西。
The term type is equivalent to the term class in all version of Python >= 2.3.
類型和非類型(或者說類和非類)都是對象,但只有類型能夠被繼承。非類型擁有具體的值,所以被繼承是毫無意義的,而且它也不能被繼承。做簡單的例子,就是類型int和它的實例2。int是類型,2是非類型。你說說,繼承2有什么意義?
是否還是會疑惑到底社么是類型?什么是非類型?
這里有一條判斷規(guī)則送給你:
如果一個對象,它的類型是“<class 'type'>”,那么,它是類型,否則不是。
還記得怎么判斷一個對象的類型嗎?沒錯的,class和type()隨意你用。
小總結:
<class 'object'>的類型是<class 'type'>;
<class 'object'>的父類為空;
<class 'type'> 的類型是它自己本身;
<class 'type'> 是<class 'object'>的子類;
在python中只要兩種對象:類型和非類型。非類型也被稱為實例。這里有英文原句,我不知怎么翻譯了,很容易看懂,但不知如何說:There are only two kinds of objects in Python: to be unambiguous let's call these types and non-types. Non-types could be called instances, but that term could also refer to a type, since a type is always an instance of another type. Types could also be called classes, and I do call them classes from time to time.
注意我們在畫板中只畫出兩個對象的直接關系,隱藏的關系就不畫了,節(jié)省我們的精力和畫板尺寸。
更多內置對象
python這條船上可不止只有兩個源對象。通過這兩個源對象可以繁育出一堆對象:
Figure 2.2. 一些內置對象
[圖片上傳失敗...(image-7957ae-1547088489991)]
上圖中的一些內置類型,下面通過例子來測試:
>>> list #====>(1)
<class 'list'>
>>> list.__class__ #====>(2)
<class 'type'>
>>> list.__bases__ #====>(3)
(<class 'object'>,)
>>> tuple.__class__,tuple.__bases__ #====>(4)
(<class 'type'>, (<class 'object'>,))
>>> dict.__class__,dict.__bases__ #和(4)一樣原理
>
(<class 'type'>, (<class 'object'>,))
>>> mylist = [1,2,3] #====>(5)
>>> mylist.__class__ #====>(6)
<class 'list'>
>>> mylist.__bases__ #====>(7)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute '__bases__'
(1):內置對象list;
(2):list的類型是<class 'type'>;
(3):list的超類是(<class 'object'>,);
(4):內置對象tuple.tiple的類型和超類分別是:<class 'type'>, (<class 'object'>,);
(5)list的一個實例mylist;
(6)實例list的類型是<class 'list'>;
(7)實例沒有超類。
我們可以創(chuàng)造一個tuple或dict的實例,但我們不能創(chuàng)造一個mylist的實例。因為mylist不是一個類型,它僅僅是一個實例。
通過繼承產生新對象
內置類型是python本身就有的。那么我們如何創(chuàng)造一個新的類型呢?
新的類型不能夠憑空產生,它必須依賴已經存在的類型,于是,繼承就呼之欲出了。
例子:通過繼承產生新對象
# In Python 2.x:
class C(object): #====>(1)
pass
# In Python 3.x, the explicit base class is not required, classes are
# automatically subclasses of object:
class C: #====>(2)
pass
class D(object):
pass
class E(C, D): #====>(3)
pass
class MyList(list): #====>(4)
pass
(1):class語句告訴python解釋器要通過一個存在的類型創(chuàng)造一個新的類型;
(2):在python3.x可以省略掉(object)。
(3):多重繼承;
(4):大多數內置類型可以被繼承,但不是所有的都可以。
通過實例化產生新對象
Example 2.5.
obj = object() #====>(1)
cobj = C() #====>(2)
mylist = [1,2,3] #====>(3)
(1),(2):利用類型名()的方式創(chuàng)造一個類型的實例。()中可能帶參數;
(3):這是python利用內置類型創(chuàng)造實例的語法。沒什么好說的。
注意:僅僅通過對<class 'object'>進行子類化,類型C就自動成為<class 'type'>的實例。原因在常見疑問的第二個問題中。
在以上的操作后,原本空白的畫板可以畫滿了:
[圖片上傳失敗...(image-e2ca94-1547088489990)]
常見疑問
到這里你頭腦中可能會有很多疑惑,下面列出其中一些問題以及答案,請酌情欣賞。有沒有提到的請留言,我會努力搜索答案來解答:
Q: Python如何真正創(chuàng)建一個新對象?
A: 在python中,創(chuàng)造的新對象有兩種:類型和非類型。類型可被繼承和實例化。非類型本事就是一個實例。當python創(chuàng)造新對象時,它會用自己本身的類型作為新對象的類型。一般會用到兩個方法__new__()和__init__()。所以。每個對象都有類型。
Q:實例化的時候要指定類型,但是當繼承時python如何知道用哪個類型?
它查看你繼承的超類,并且使用超類的類型來作為新對象的類型。
在大多數的情況下,<class 'object'>的子類(和<class 'object'>的子類的子類等等)的類型都是<class 'type'>
>>> class A(object):
... pass
...
>>> class B(A):
... pass
...
>>> class C(B):
... pass
...
>>> A.__class__, B.__class__, C.__class__
(<class 'type'>, <class 'type'>, <class 'type'>)
Q:我能夠創(chuàng)造一個新的類型?
A:能,這就得元類出場了,通過屬性__metaclass__你可以重新創(chuàng)造一個類型出來。這里我簡單列一個例子。元類的話下面會簡單介紹。
>>> class A(type):
... pass
...
>>> class B(object, metaclass=A):
... pass
...
>>> class C(B):
... pass
...
>>> A.__class__, B.__class__, C.__class__
(<class 'type'>, <class '__main__.A'>, <class '__main__.A'>)
通過繼承type,我們創(chuàng)造出新的類型<class '__main__.A'>。
Q:wow!那我可以使用任何的類型作為metaclass的參數嗎?
A:不能。只有繼承了type的類能夠做為metaclass的參數。
Q:我應該使用metaclass嗎?
不建議使用。高手除外。
準備結束
一幅描繪python對象的圖
我們最后得到一幅由不同對象組成的地圖:
[圖片上傳失敗...(image-ee32a7-1547088489989)]
在大多數情況之下,我們都是學習第二列和第三列的內容。至于第一列,那是元類的領域了。不是所有人都要深入學習。
來解釋一下上圖的東西:
虛線可以從一列穿過另一列,例如從實例所在列穿到類所在列。(<type 'type'>例外);
實線不可以穿過其他列。再一次地,<type 'type'> -> <type 'object'> 是例外。
第三列不允許出現實線。因為實線代表繼承。第三列地實例無法子類化;
第三列地對象也不允許被實例化;
第一,二列包含
類型,第三列包含非類型;如果創(chuàng)造一個繼承<class 'type'>的對象,那么它會被放在第一列,即元類。這里繼續(xù)強調,類和類型是一樣的。
<class 'type'>和<type 'type'>也是一樣的。
注意:<class 'type'>是所有類型的類型。<class 'object'>也是所有對象的超類(除了它自己)。
總結
這些內容是對前面的總結:
- 在python中有兩種對象:
類型對象:可以被實例化和繼承;
非類型對象:不可以被實例和繼承。
<class 'type'>和<class 'object'>是python中的兩個源對象。
每個對象都有類型。用objectname.class查看。
每個類型對象都有超類(object除外),用objectname.bases可以查看。
通過繼承產生的新對象都是類型對象。繼承是用class語句來實現的。
通過實例化產生的新對象可能是類型對象,也可能是非類型對象。實例化是通過調用操作符
()來實現的。一些python的非類型對象可以通過特殊的語法來創(chuàng)造。例如[1, 2, 3]是list的實例。
在內部,python總是使用類型對象來創(chuàng)造新對象。新創(chuàng)造的對象是該類型對象的實例。(在這里,實例有兩種意思:一通過繼承產生的子類,二是通過實例化產生的具體實例。但平時我們說的實例就是只第二種)。python通過class語句中
指定的超類的類型來決定新對象的類型。issubclass(A,B)返回true當且僅當:
B在A.bases輸出的元組之中;
如果A在Z.bases輸出的元組中,issubclass(Z,B)返回true.
-
isinstance(A,B)返回true當且僅當:
A.class是B,或者
issubclass(A.class,B)返回true.