該系列文章:python
在上一篇文章《python中的數據類型(list,tuple,dict,set,None)》的1.2小節裏咱們就簡要介紹過對象(object)跟類(class)的概念。也知道了python中內置的全部數據類型都是對象,擁有本身的方法。那麼當這些內置的數據類型沒法知足咱們的需求時,咱們如何建立咱們本身的類型(type)呢?答案就是經過建立咱們本身的類(class)。經過咱們本身動手實現的類,咱們就能夠建立以這個類爲模板的對象。從這樣的流程來看,面向對象的編程方式是自頂而下,首先須要全盤考慮,才能建立一個足夠好的模板,也即類。而後才能將類實例化爲對象,經過對象中的屬性來解決問題或者與其餘對象互動。git
建立一個最簡單的類能夠經過下面這樣的寫法:github
class User:
pass
#更多代碼
#更多代碼
複製代碼
上面的代碼中class是關鍵字,代表咱們要建立一個類了,User是咱們要建立的類的名稱。經過「:」和縮進來代表全部縮進的代碼將會是這個類裏的內容。從User類中建立一個該類的實例經過下面的寫法:編程
""" 建立一個實例,經過類名加括號的形式,相似調用函數 """
u=User()
複製代碼
對象(客體)有本身的特徵和本身能夠作到的事,對應到程序裏就是字段(field) 和方法(method) ,這兩個都是對象的屬性(attribute) 。對象的字段相似於普通變量,所不一樣的是對象的字段是對象獨有的。對象的方法相似於普通函數,所不一樣的是對象的方法是對象獨有的。上篇文章中咱們已經見到過如何使用字段跟方法,那就是經過.
操做符。bash
在類中定義對象的方法(method)比較簡單,跟實現普通函數相似,只有一點不一樣,那就是無論方法需不須要參數,你都須要把self
做爲一個參數名傳進去,self
這個參數在咱們調用方法時咱們能夠直接忽略,不賦值給它。舉個例子:ssh
class User:
def hi(self):
print("hi!")
u=User()
u.hi()
""" 程序輸出: hi! """
複製代碼
self
這個參數名是約定俗成的。在User
類的代碼塊裏定義hi
方法時,傳入的參數self
將會是某個實例(對象)自己。當u
做爲User
類的實例被建立,而且經過u.hi()
調用hi
方法時,python解釋器會自動將其轉換成User.hi(u)
。經過傳入實例(對象)自己,也即self
,方法(method)就可以訪問實例的字段(filed),並對其進行操做,咱們以後能夠重新的例子中看到。函數
要在類中聲明對象的字段,有一個特殊的方法(method)能夠作到,那就是__init__
方法,這個方法在init
先後都要寫上兩個下劃線__
。__init__
方法會在實例一開始建立的時候就被調用,init
是initialization
的縮寫,顧名思義,就是初始化的意思。__init__
方法在建立對象的時候由python解釋器自動調用,不須要咱們手動來調用。看個例子:post
class User:
""" 注意self老是在括號裏的最左邊 """
def __init__(self,name,age):
self.name=name
self.age=age
def hi(self):
print("hi!I'm {}".format(self.name))
u=User("li",32)
u.hi()
print(u.name+","+str(u.age))
""" 程序輸出: hi!I'm li li,32 """
複製代碼
上面的代碼裏,在User
類的__init__
方法中self
被傳入,那麼就能夠經過self.name
跟self.age
來聲明對象的兩個字段,並將傳入該方法的參數name
跟age
賦值給它們。當咱們建立類型爲User
的對象的時候,傳入實際的參數"li"
跟32
,這兩個參數被python解釋器傳入__init__
方法中,"li"
對應name
,32
對應age
,__init__
方法當即被調用,將實例u
的字段一一創建。ui
self.name=name
粗看貌似都是name
變量,但self.name是實例的字段,專屬於實例,name
是建立對象時將要傳入的一個參數,將它賦值給self.name
是沒有歧義的。spa
事實上,字段除了獨屬於實例以外,跟普通變量沒有什麼差異,因此實例的字段也被稱爲實例變量。在類的定義中,與實例變量對應的還有類變量,類變量與實例變量相似,經過.
操做符來訪問。類變量是任何實例共享的,能夠理解爲是該類型所共有的特徵,好比,在User
類中,咱們能夠計算一共有多少被實例化了的用戶:
class User:
""" 計算被實例化的用戶數量 """
count=0
def __init__(self,name,age):
"""每次建立一個對象,用戶數量在原有基礎上加1"""
User.count=User.count+1
self.name=name
self.age=age
def hi(self):
print("hi!I'm {}".format(self.name))
def die(self):
print("I'm {}, dying...".format(self.name))
User.count=User.count-1
del self
@classmethod
def print_count(cls):
print("共有{}名用戶".format(cls.count))
u=User("li",32)
User.print_count()
u.hi()
u1=User("ma",30)
u1.__class__.print_count()
u1.hi()
u.die()
User.print_count()
u1.die()
User.print_count()
""" 程序輸出: 共有1名用戶 hi!I'm li 共有2名用戶 hi!I'm ma I'm li, dying... 共有1名用戶 I'm ma, dying... 共有0名用戶 """
複製代碼
上面代碼中的count
就是類變量,能夠經過User.count
來訪問。python經過@classmethod
來代表它下面定義的方法是類的方法(method),類的方法中的cls
是類自己,跟self
使用方法相似,調用類方法時能夠直接忽略。類變量跟類的方法(method)均可以被稱爲類的成員。除了使用相似User.count
這樣的方式來訪問和使用以外,該類的實例還能夠經過__class__
屬性來訪問和使用類成員,好比上面代碼中的u1.__class__.print_count()
。
上面代碼中定義的字段跟方法都是公開的,能夠經過.
操做符訪問。但若是屬性是以形如__name
這樣以雙下劃線爲開頭的名稱,則python會自動將名稱換成_classname__name
,其中classname
就是類的名稱,這樣,經過an_object.__name
是訪問不到的。
現實世界裏,某一類類似客體的類型被抽象爲一個概念(名稱),同時又有另外一類類似客體的類型被抽象爲一個概念(名稱),這時候咱們可能會發現,這兩個概念(名稱)之間很類似,因而咱們把這兩個類似概念再抽象成一個概念(名稱),這樣的過程能夠重複屢次。舉個例子,咱們有幼兒園,同時有小學,這時候咱們能夠把幼兒園跟小學抽象成學校。那跟現實相似,對象的類型跟類型之間,能夠抽象成另外一個類型。在文章最開頭咱們說面向對象編程是自頂而下,這跟一層層向上抽象的過程正好相反,咱們會在一開始思考如何建立某個類,而後把這個類做爲基類(父類) ,更具體的建立一(幾)個新類,這一(幾)個新類不只擁有基類的屬性,還會增長本身獨有的屬性。咱們把這樣的新類(class)叫作子類,是從基類繼承而來的。
從1.2小節的代碼例子中,咱們建立了一個User
(用戶)類,假如咱們如今須要區分免費用戶和付費用戶,免費用戶跟付費用戶都具備name
、age
、hi
屬性,同時免費用戶跟付費用戶還具備本身獨有的一些屬性。若是咱們直接分別建立免費用戶類跟付費用戶類,那麼這兩個類之間共同的屬性就會被定義兩次,須要複製粘貼一樣的代碼,這樣的代碼質量不高而且不夠簡潔。相反,咱們可讓免費用戶跟付費用戶都從User
類繼承下來,這樣就能夠重用(reuse) User
類的代碼,而且讓代碼相對簡潔高效一點。還有一個好處就是,當免費用戶跟付費用戶之間共同的屬性有了變化(增減),咱們能夠直接修改父類User
,而不用分別修改,當子類的數量不少時,這種好處會顯得很是明顯。修改父類以後,各子類的獨有屬性不會受到影響。
1.2小節的代碼例子中,咱們對實例的數量進行統計,當子類從User
類繼承下來後,在子類實例化對象的時候,父類的實例數量在python中默認也會增多,這說明咱們能夠把子類的實例看作是父類的實例,這被稱爲多態性(polymorphism)。免費用戶類跟付費用戶類如何從父類繼承,以下:
class User:
""" 計算被實例化的用戶數量 """
count=0
def __init__(self,name,age):
"""每次建立一個對象,用戶數量在原有基礎上加1"""
User.count=User.count+1
self.name=name
self.age=age
def hi(self):
print("hi!I'm {}".format(self.name))
def die(self):
print("I'm {}, dying...".format(self.name))
User.count=User.count-1
del self
@classmethod
def print_count(cls):
print("共有{}名用戶".format(cls.count))
class Free_user(User):
def __init__(self,name,age,number_of_ads):
User.__init__(self,name,age)
self.number_of_ads=number_of_ads
def hi(self):
User.hi(self)
print("I'm free_user")
class Paying_user(User):
def __init__(self,name,age,plan):
User.__init__(self,name,age)
self.plan=plan
def hi(self):
print("hi!I'm {},paying_user".format(self.name,))
u=Free_user("li",32,5)
User.print_count()
u.hi()
u1=Paying_user("ma",30,"5$")
User.print_count()
u1.hi()
u.die()
User.print_count()
u1.die()
User.print_count()
""" 程序輸出: 共有1名用戶 hi!I'm li I'm free_user 共有2名用戶 hi!I'm ma,paying_user I'm li, dying... 共有1名用戶 I'm ma, dying... 共有0名用戶 """
複製代碼
上面代碼中首先建立了User
基類,而後從User
類繼承下來兩個子類:Free_user
跟Paying_user
。子類要從某個類繼承而來,須要在類名後面跟上括號,在括號中填入基類的名稱,形如這樣:Free_user(User)
。
在子類的__init__
方法中,經過調用基類的__init__
方法把繼承自基類的共有字段建立出來,調用的時候將self
跟傳入的屬於基類部分的參數原樣傳入,上面代碼中將傳入的name
跟age
原樣傳入了基類的__init__
方法中了。由於咱們在子類中定義了__init__
方法,因此python不會自動調用基類的__init__
方法,而須要咱們顯式地調用它。若是子類中沒有定義__init__
方法,則python會在建立子類的實例時自動調用基類的__init__
方法。這是爲何呢?咱們在子類中對基類的hi
方法進行了重寫,但對die
方法則沒有,可是咱們經過子類建立的實例可以調用die
方法,這說明在調用die
方法時子類的實例被看作了父類的實例了,同時在調用hi
方法時卻都調用了重寫的子類中的方法了,這說明,當實例調用一個方法時,會首先在實例化本身的類中尋找對該方法的定義,若是沒有,則在該類的父類中尋找,若是父類中仍是沒有,則會在父類的父類中尋找,這樣的過程一直重複,直到再也沒有父類了,若是仍是沒有該方法,那程序就會報錯。
最後,從前一小節咱們知道,以雙下劃線爲前綴的屬性名會被python自動替換成另外一個名稱,這時候當父類中有個以雙下劃線爲前綴的屬性,咱們在子類中也有一個相同的屬性的時候,因爲python自動替換了這兩個屬性名稱,子類中的方法並無覆蓋掉父類中的屬性,而只是,子類中的該屬性跟父類中的該屬性是兩個不一樣的屬性。看例子:
#其餘一些屬性用「,」省略了
>>> class a:
... def __a_method(self):
... pass
...
>>> dir(a())
['__class__', ,,, '_a__a_method']
>>> class b(a):
... def __a_method(self):
... pass
...
>>> dir(b())
['__class__', ,,, '_a__a_method', '_b__a_method']
>>>
複製代碼
能夠看到,__a_method
在a
類的實例中被替換成了_a__a_method
,在b
類的實例中被替換成了_b__a_method
,這跟_a_a_method
不一樣,b
類的實例中繼承了_a_a_method
,同時,還有本身類型的_b_a_method
。
看個例子:
>>> list.__base__
<class 'object'>
>>> type(list)
<class 'type'>
>>> class User:
... pass
...
>>> User.__base__
<class 'object'>
>>> type(list)
<class 'type'>
複製代碼
類的屬性__base__
能夠指明該類是繼承自哪一個類。從上面的例子能夠看到,python中定義內置數據類型的類(class
)(以list
爲例)從object
類繼承而來,可是其類型(type)是type
類,咱們本身建立而且沒有明確指定從是哪一個類繼承而來的類(以User
爲例),跟內置類型同樣,從object
類繼承而來,其類型(type)是type
類。若是咱們本身建立的,而且從層次更低的類(好比User
類)中繼承而來,那麼該類的__base__
(基類)跟type函數所顯示的類型是怎麼樣的呢,接上面的例子:
>>> Free_user.__base__
<class '__main__.User'>
>>> type(Free_user)
<class 'type'>
>>>
複製代碼
能夠看到,Free_user
從User
繼承而來,但其類型依然是type
,因此全部的類(class)的類型應當都是type
,是否是這樣呢,object
會不會例外呢?type
本身呢?它的類型呢?下面的例子驗證了它們的類型也是type
:
>>> type(object)
<class 'type'>
>>> type(type)
<class 'type'>
複製代碼
從例子中咱們知道了,全部的類(class
)的類型都是type
類,因此,全部的類(class
)都是從type
類中實例化出來的實例,甚至type
類實例化了本身,因此全部的類包括object
和type
都是實例,這說明全部的類都是對象 。爲了清晰的指代不一樣種類的對象,咱們把自己是類的對象稱爲類對象,把從類中實例化而來而且自己不是類的對象成爲實例對象,實例對象是層次最低的對象。
咱們能夠看到object
類跟type
類是python中抽象層次最高的對象了。那麼它們兩個的關係如何呢?看例子:
>>> type.__base__
<class 'object'>
>>> print(object.__base__)
None
>>>
複製代碼
能夠看到,type
從object
繼承而來,而且咱們已經知道object
的類型是type
。從例子中能夠看到object
沒有基類,因此若是把類的層層繼承想象成一條鎖鏈,那麼object
將是繼承鏈上的頂點。前面咱們提到的對象的一些特殊屬性如__init__
、__class__
繼承自object
,而__base__
這個特殊屬性則只有類對象纔有,是在type
中定義的。以下:
#其餘一些屬性用「,」省略了
>>> dir(object)
['__class__', ,,, '__init__', ,,, '__subclasshook__']
>>> dir(type)
['__abstractmethods__', '__base__', '__bases__', ,,, 'mro']
>>>
複製代碼