在python中建立對象(object)

該系列文章: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

1.0.定義方法(method)

在類中定義對象的方法(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),並對其進行操做,咱們以後能夠重新的例子中看到。函數

1.1.聲明字段(field)

要在類中聲明對象的字段,有一個特殊的方法(method)能夠作到,那就是__init__方法,這個方法在init先後都要寫上兩個下劃線____init__方法會在實例一開始建立的時候就被調用,initinitialization的縮寫,顧名思義,就是初始化的意思。__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.nameself.age來聲明對象的兩個字段,並將傳入該方法的參數nameage賦值給它們。當咱們建立類型爲User的對象的時候,傳入實際的參數"li"32,這兩個參數被python解釋器傳入__init__方法中,"li"對應name32對應age__init__方法當即被調用,將實例u的字段一一創建。ui

self.name=name粗看貌似都是name變量,但self.name是實例的字段,專屬於實例,name是建立對象時將要傳入的一個參數,將它賦值給self.name是沒有歧義的。spa

1.2.實例變量與類變量

事實上,字段除了獨屬於實例以外,跟普通變量沒有什麼差異,因此實例的字段也被稱爲實例變量。在類的定義中,與實例變量對應的還有類變量,類變量與實例變量相似,經過.操做符來訪問。類變量是任何實例共享的,能夠理解爲是該類型所共有的特徵,好比,在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是訪問不到的。

1.3.繼承(inherit)

現實世界裏,某一類類似客體的類型被抽象爲一個概念(名稱),同時又有另外一類類似客體的類型被抽象爲一個概念(名稱),這時候咱們可能會發現,這兩個概念(名稱)之間很類似,因而咱們把這兩個類似概念再抽象成一個概念(名稱),這樣的過程能夠重複屢次。舉個例子,咱們有幼兒園,同時有小學,這時候咱們能夠把幼兒園跟小學抽象成學校。那跟現實相似,對象的類型跟類型之間,能夠抽象成另外一個類型。在文章最開頭咱們說面向對象編程是自頂而下,這跟一層層向上抽象的過程正好相反,咱們會在一開始思考如何建立某個類,而後把這個類做爲基類(父類) ,更具體的建立一(幾)個新類,這一(幾)個新類不只擁有基類的屬性,還會增長本身獨有的屬性。咱們把這樣的新類(class)叫作子類,是從基類繼承而來的。

從1.2小節的代碼例子中,咱們建立了一個User(用戶)類,假如咱們如今須要區分免費用戶和付費用戶,免費用戶跟付費用戶都具備nameagehi屬性,同時免費用戶跟付費用戶還具備本身獨有的一些屬性。若是咱們直接分別建立免費用戶類跟付費用戶類,那麼這兩個類之間共同的屬性就會被定義兩次,須要複製粘貼一樣的代碼,這樣的代碼質量不高而且不夠簡潔。相反,咱們可讓免費用戶跟付費用戶都從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_userPaying_user。子類要從某個類繼承而來,須要在類名後面跟上括號,在括號中填入基類的名稱,形如這樣:Free_user(User)

在子類的__init__方法中,經過調用基類的__init__方法把繼承自基類的共有字段建立出來,調用的時候將self跟傳入的屬於基類部分的參數原樣傳入,上面代碼中將傳入的nameage原樣傳入了基類的__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_methoda類的實例中被替換成了_a__a_method,在b類的實例中被替換成了_b__a_method,這跟_a_a_method不一樣,b類的實例中繼承了_a_a_method,同時,還有本身類型的_b_a_method

1.4.類(class),對象(object)與類型(type)之間的關係

看個例子:

>>> 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_userUser繼承而來,但其類型依然是type,因此全部的類(class)的類型應當都是type,是否是這樣呢,object會不會例外呢?type本身呢?它的類型呢?下面的例子驗證了它們的類型也是type

>>> type(object)
<class 'type'>
>>> type(type)
<class 'type'>

複製代碼

從例子中咱們知道了,全部的類(class)的類型都是type類,因此,全部的類(class)都是從type類中實例化出來的實例,甚至type類實例化了本身,因此全部的類包括objecttype都是實例,這說明全部的類都是對象 。爲了清晰的指代不一樣種類的對象,咱們把自己是類的對象稱爲類對象,把從類中實例化而來而且自己不是類的對象成爲實例對象,實例對象是層次最低的對象。

咱們能夠看到object類跟type類是python中抽象層次最高的對象了。那麼它們兩個的關係如何呢?看例子:

>>> type.__base__
<class 'object'>
>>> print(object.__base__)
None
>>> 

複製代碼

能夠看到,typeobject繼承而來,而且咱們已經知道object的類型是type。從例子中能夠看到object沒有基類,因此若是把類的層層繼承想象成一條鎖鏈,那麼object將是繼承鏈上的頂點。前面咱們提到的對象的一些特殊屬性如__init____class__繼承自object,而__base__這個特殊屬性則只有類對象纔有,是在type中定義的。以下:

#其餘一些屬性用「,」省略了
>>> dir(object)
['__class__', ,,, '__init__', ,,, '__subclasshook__']
>>> dir(type)
['__abstractmethods__', '__base__', '__bases__', ,,, 'mro']
>>> 

複製代碼

歡迎瀏覽個人我的博客,https://diwugebingren.github.io

歡迎關注個人公衆號
相關文章
相關標籤/搜索