Python 中的面向?qū)ο?/h1>

面向?qū)ο笥腥齻€特征:封裝毛甲、繼承、多態(tài)哈扮。
封裝:將一些列功能和屬性集合在對象中纬纪,以實(shí)現(xiàn)代碼復(fù)用和功能的解耦。
繼承:每個類都可以有父類灶泵,父類中定義了常用的方法育八,子類可以對這些方法進(jìn)行擴(kuò)展,并可以定義自己的方法赦邻。
多態(tài):子類都可以對父類的方法進(jìn)行覆寫髓棋,同樣繼承于父類的方法,不同的子類可以有不同的表現(xiàn)形式惶洲,這就是多態(tài)按声。

定義類

使用 class 關(guān)鍵字定義類,一般建議類名采用大駝峰式寫法:

class Demo:
    pass

定義方法

定義方法和定義函數(shù)一樣恬吕,都采用 def 關(guān)鍵字签则,每個方法都至少需要接受一個參數(shù),該參數(shù)為調(diào)用當(dāng)前方法的實(shí)例對象的一個引用:

class Demo:
    def __init__(self,name):
        self.name = name
    def show(self):
        print("My name is %s"%self.name)

繼承

Python 中沒有 extends 關(guān)鍵字铐料,使用繼承時直接在類名后加上括號渐裂,括號中寫上需要繼承的父類豺旬,支持多繼承。

class Father(object):
    pass
class Mother(object):
    pass
class Son(Father,Mother):
    pass

__mro__ 屬性

在調(diào)用實(shí)例方法時柒凉,首先看子類中有沒有該方法族阅,如果沒有,則去看父類中有沒有該方法膝捞,如果父類中也沒有該方法坦刀,則去看父類的父類中有沒有該方法,一直追溯到 object 類為止蔬咬。
使用類的 __mro__ 屬性鲤遥,可以幫助我們明確方法的查找順序:

class Father(object):
    pass
class Mother(object):
    pass
class Son(Father,Mother):
    pass
print(Son.__mro__)

執(zhí)行結(jié)果如下:

(<class '__main__.Son'>, <class '__main__.Father'>, <class '__main__.Mother'>, <class 'object'>)

私有屬性和私有方法

Python 中沒有 private 關(guān)鍵字,要定義實(shí)例的私有屬性和私有方法林艘,只需在相應(yīng)的屬性名或方法名前面加上兩根下劃線:

class XiaoMei:
    def __init__(self,age):
        self.__age = age    

私有屬性和方法只能在當(dāng)前類的內(nèi)部被調(diào)用盖奈,無法在類的外部或者子類中被調(diào)用,如需調(diào)用私有屬性和方法狐援,只需暴露出一個公共方法卜朗,在公共方法內(nèi)部對私有屬性和方法進(jìn)行訪問:

class XiaoMei:
    def __init__(self,age):
        self.__age = age

    def getAge(self):
        return self.__age

print(XiaoMei(18).getAge())

__del__ 方法

__del__ 方法在對象被銷毀前調(diào)用,對象被銷毀有兩種情況:

  • 程序運(yùn)行結(jié)束后自動銷毀對象
  • 手動通過 del 關(guān)鍵字銷毀對象

下面分別看下這幾種情況:

class 領(lǐng)導(dǎo):
    def __del__(self):
        print("SB 領(lǐng)導(dǎo)要被銷毀啦咕村!")

領(lǐng)導(dǎo)1 = 領(lǐng)導(dǎo)()
print("="*50)

運(yùn)行結(jié)果為:

==================================================
SB 領(lǐng)導(dǎo)要被銷毀啦场钉!

上面是程序運(yùn)行結(jié)束自動銷毀對象的情況,下面看一下手動銷毀對象的情況:

class 領(lǐng)導(dǎo):
    def __del__(self):
        print("SB 領(lǐng)導(dǎo)要被銷毀啦懈涛!")

領(lǐng)導(dǎo)1 = 領(lǐng)導(dǎo)()
del 領(lǐng)導(dǎo)1
print("="*50)

運(yùn)行結(jié)果如下:

SB 領(lǐng)導(dǎo)要被銷毀啦逛万!
==================================================

可見,手動使用 del 關(guān)鍵字刪除對象也會執(zhí)行 __del__ 方法批钠。

對象的引用

Python 中的一切賦值都是按引用傳遞的宇植,如果一個實(shí)例有多個引用,那么 __del__ 方法將在所有的引用斷開后再執(zhí)行埋心。這是因?yàn)?Python 中的垃圾回收是按照引用計數(shù)來的指郁,當(dāng)一份內(nèi)存沒有任何引用時,其就會被垃圾回收拷呆。

class 領(lǐng)導(dǎo):
    def __del__(self):
        print("SB 領(lǐng)導(dǎo)要被銷毀啦闲坎!")

領(lǐng)導(dǎo)1 = 領(lǐng)導(dǎo)()
領(lǐng)導(dǎo)2 = 領(lǐng)導(dǎo)1
del 領(lǐng)導(dǎo)1
print("="*50)

運(yùn)行結(jié)果如下:

==================================================
SB 領(lǐng)導(dǎo)要被銷毀啦!

稍微修改一下代碼:

class 領(lǐng)導(dǎo):
    def __del__(self):
        print("SB 領(lǐng)導(dǎo)要被銷毀啦茬斧!")

領(lǐng)導(dǎo)1 = 領(lǐng)導(dǎo)()
領(lǐng)導(dǎo)2 = 領(lǐng)導(dǎo)1
del 領(lǐng)導(dǎo)1
del 領(lǐng)導(dǎo)2
print("="*50)

運(yùn)行結(jié)果如下:

SB 領(lǐng)導(dǎo)要被銷毀啦腰懂!
==================================================

__del__ 方法在 print 之前被調(diào)用,這是因?yàn)槲覀冋{(diào)用兩次 del 方法后刪除了對象所在內(nèi)存空間的所有引用项秉,當(dāng)這片內(nèi)存不再被引用時偷厦,其就會被垃圾回收了屁奏。

獲取對象的引用計數(shù)

我們可以通過 sys 模塊下的 getrefcount 方法來查看對象的引用計數(shù):

import sys
class 領(lǐng)導(dǎo):
    pass

領(lǐng)導(dǎo)1 = 領(lǐng)導(dǎo)()
領(lǐng)導(dǎo)2 = 領(lǐng)導(dǎo)1

print(sys.getrefcount(領(lǐng)導(dǎo)1))
print(sys.getrefcount(領(lǐng)導(dǎo)2))

運(yùn)行結(jié)果如下:

3
3

我們知道領(lǐng)導(dǎo)類的實(shí)例有兩份引用获搏,但為什么調(diào)用 getrefcount 方法的返回值是 3 呢?這是因?yàn)槲覀儠?shí)例作為參數(shù)傳入 getrefcount 方法中底哗,該參數(shù)也是實(shí)例的一份引用,我們向函數(shù)中傳參傳入的都是引用锚沸,所以這里的引用數(shù)量就是 3艘虎,沒毛病。

子類調(diào)用父類中被覆寫的方法

子類調(diào)用父類中被覆寫的方法有兩種方式:

  • 通過父類類名調(diào)用
  • 通過 super 關(guān)鍵字

通過父類類名調(diào)用咒吐,需要傳入傳入當(dāng)前對象的引用 self:

class Animal(object):
    def walk(self):
        print("走走~")

class Cat(Animal):
    def walk(self):
        Animal.walk(self)
        print("跳著走走~")

Cat().walk()

通過 super 關(guān)鍵字調(diào)用無需傳入當(dāng)前對象的引用:

class Animal(object):
    def walk(self):
        print("走走~")

class Cat(Animal):
    def walk(self):
        super().walk()
        print("跳著走走~")

Cat().walk()

多態(tài)

前面說到過,多態(tài)就是對于父類的同一份方法属划,不同的子類有不同的實(shí)現(xiàn)恬叹,在執(zhí)行時會產(chǎn)生不同的結(jié)果。

class Animal(object):
    def eat(self):
        print("我需要吃點(diǎn)什么……")

class Jerry(Animal):
    def eat(self):
        print("我喜歡吃奶酪")

class Tom(Animal):
    def eat(self):
        print("我喜歡吃 Jerry")

def eatSomeThing(animal):
    animal.eat()

eatSomeThing(Animal())
eatSomeThing(Jerry())
eatSomeThing(Tom())

執(zhí)行結(jié)果如下:

我需要吃點(diǎn)什么……
我喜歡吃奶酪
我喜歡吃 Jerry

多態(tài)的本質(zhì)就是繼承和方法覆寫同眯。

類屬性和類方法

類屬性和類方法直接從屬于類绽昼,而不是類的實(shí)例對象。
定義類的屬性须蜗,直接在類中進(jìn)行聲明賦值操作即可硅确。
定義類的方法,需要在方法上加上 @classmethod 裝飾器明肮,方法的首參數(shù)表示當(dāng)前的類對象菱农。

class Demo:
    # 定義類屬性
    count = 0
    def __init__(self):
        Demo.count += 1

    # 定義類方法
    @classmethod
    def getCount(cls):
        print("已經(jīng)創(chuàng)建了 %d 個實(shí)例"%cls.count)

Demo()
Demo()
Demo()
Demo()
Demo()
Demo.getCount()

運(yùn)行結(jié)果如下:

已經(jīng)創(chuàng)建了 5 個實(shí)例

類實(shí)例和類方法可以被類對象調(diào)用,也可以被類對象的實(shí)例對象調(diào)用柿估。

類對象和實(shí)例對象

對象可以看作是一系列屬性和方法的集合體循未,除了我們新建的實(shí)例對象外,類本身也可以擁有自身的屬性和方法秫舌,那么這個類也可以叫做對象的妖。我們可以把類叫做類對象,把類的實(shí)例叫做實(shí)例對象足陨。

類對象和實(shí)例對象之間的關(guān)系

實(shí)例對象之間是相互獨(dú)立的嫂粟,比如,它們都有自己獨(dú)特的屬性墨缘,修改某一實(shí)例的屬性并不會影響到其他的實(shí)例:

class Animal(object):
    def __init__(self,name):
        self.name = name
    def showName(self):
        print(self.name)

tom = Animal("Tom")
jerry = Animal("Jerry")

tom.showName()
jerry.showName()

運(yùn)行結(jié)果:

Tom
Jerry

調(diào)用 tom 實(shí)例的 showName 方法并不會打印 jerry 實(shí)例的 name 屬性星虹,這是因?yàn)檫@兩個實(shí)例是獨(dú)立的。
實(shí)例的屬性和方法從哪里來呢镊讼?一般認(rèn)為是在新建實(shí)例對象的時候搁凸,實(shí)例對象向類對象索取一份方法的拷貝,每個實(shí)例內(nèi)部都保存有自身的方法和屬性狠毯,因此實(shí)例之間調(diào)用方法時不會相互影響护糖。
讓每個實(shí)例對象內(nèi)部保存一份類對象中方法的拷貝,確實(shí)是很直接的一種方式嚼松,但這會增加內(nèi)存空間的消耗嫡良,比如我們做一個打飛機(jī)的游戲锰扶,我們會一直新建敵機(jī),那就會一直拷貝類對象中的屬性和方法寝受,增加內(nèi)存的消耗坷牛。
事實(shí)上,實(shí)例對象在調(diào)用方法時很澄,并不是調(diào)用自身內(nèi)存空間中的方法京闰,新建實(shí)例時也不會有方法的拷貝,而是去調(diào)用類對象中的方法甩苛。實(shí)例對象怎么找到其對應(yīng)的類對象呢蹂楣?我們可以認(rèn)為在新建實(shí)例對象時,其內(nèi)部保存了一份類對象的引用讯蒲,在調(diào)用方法時痊土,通過這個引用找到類對象,并調(diào)用類對象中的方法墨林。同時赁酝,將實(shí)例對象的一份引用傳遞給類對象中的方法,在方法中通過改實(shí)例對象的引用對實(shí)例對象進(jìn)行一些操作旭等。
示意圖如下:

類對象和實(shí)例對象之間的引用關(guān)系.png

現(xiàn)在知道為什么實(shí)例對象的方法至少需要一個實(shí)例的引用參數(shù)酌呆,以及在繼承中可以通過 父類類名.父類中的方法( 實(shí)例引用 ) 調(diào)用父類對象中的方法的原因了吧。

__new__ 方法和單例模式

__new__ 方法用來在新建對象時調(diào)用搔耕,Python 中創(chuàng)建對象分為兩個階段:

  • 新建對象引用(分配內(nèi)存空間)
  • 初始化對象

新建對象引用由 __new__ 方法完成肪笋,初始化對象由 __init__ 方法完成。__init__ 方法需要接收 __new__ 方法返回的新對象引用作為參數(shù)度迂,否則無法完成初始化操作藤乙。__new__ 方法接收當(dāng)前類對象作為參數(shù)。
看下面的代碼:

class Demo(object):
    def __new__(cls):
        print("__new__")

    def __init__(self):
        print("__init__")

    def __del__(self):
        print("__del__")

Demo()

運(yùn)行結(jié)果如下:

__new__

只有 __new__ 方法被調(diào)用了惭墓,其余的 __init__ 方法和 __del__ 方法并未被調(diào)用坛梁。因?yàn)槲覀冎貙懥?__new__ 方法,但在該方法中并未返回新建對象的引用腊凶,創(chuàng)建對象的第一步都沒有完成划咐,因此不會再執(zhí)行后續(xù)的方法了。

調(diào)用超類的 __new__ 方法

由于我們并不知道 __new__ 方法的實(shí)現(xiàn)細(xì)節(jié)钧萍,我們可以直接調(diào)用超類的 __new__ 方法:

class Demo(object):
    def __new__(cls):
        print("__new__")
        # 返回新建對象的引用
        return object.__new__(cls)

    def __init__(self):
        print("__init__")

    def __del__(self):
        print("__del__")

Demo()

執(zhí)行結(jié)果如下:

__new__
__init__
__del__

可見其余的兩個方法也被調(diào)用了褐缠。

__init__ 并不是構(gòu)造方法

構(gòu)造方法是創(chuàng)建并初始化對象的方法,因?yàn)?Python 中創(chuàng)建和初始化對象是使用兩個方法完成的风瘦,因此 Python 中是沒有構(gòu)造方法的队魏。

利用 __new__ 實(shí)現(xiàn)單例模式

單例模式就是一個實(shí)例的模式,只初始化類對象一次『埃可以借助 __new__ 方法來實(shí)現(xiàn)單例模式:

class Demo(object):
    __singleInstance = None
    def __new__(cls):
        # 判斷類的單例屬性是否存在
        if cls.__singleInstance:
            return cls.__singleInstance
        else:
            cls.__singleInstance = object.__new__(cls)
            return cls.__singleInstance

print(id(Demo()))
print(id(Demo()))
print(id(Demo()))

運(yùn)行結(jié)果如下:

14965232
14965232
14965232

可見官帘,這些實(shí)例對象的地址都是一樣的,這就是單例模式昧谊。

更好的單例模式

上面的單例模式有個小問題刽虹,就是可以重復(fù)初始化:

class Demo(object):
    __singleInstance = None
    def __new__(cls,name):
        # 判斷類的單例屬性是否存在
        if cls.__singleInstance:
            return cls.__singleInstance
        else:
            cls.__singleInstance = object.__new__(cls)
            return cls.__singleInstance

    def __init__(self,name):
        self.name = name

d1 = Demo("D1")
d2 = Demo("D2")
d3 = Demo("D3")

print(d1.name,d2.name,d3.name)

運(yùn)行結(jié)果如下:

D3 D3 D3

每次初始化實(shí)例的時候都會對實(shí)例的 name 屬性進(jìn)行覆蓋,我們想要單例在創(chuàng)建后無法進(jìn)行更改呢诬,可以在 __init__ 方法中加上一個判斷:

class Demo(object):
    __singleInstance = None
    __created = False
    def __new__(cls,name):
        # 判斷類的單例屬性是否存在
        if cls.__singleInstance:
            return cls.__singleInstance
        else:
            cls.__singleInstance = object.__new__(cls)
            return cls.__singleInstance

    def __init__(self,name):
        if not Demo.__created:
            self.name = name
            Demo.__created = True

d1 = Demo("D1")
d2 = Demo("D2")
d3 = Demo("D3")

print(d1.name,d2.name,d3.name)

運(yùn)行結(jié)果如下:

D1 D1 D1

單例對象的屬性再也不會被覆蓋了涌哲。

總結(jié)

本文談到了 Python 中面向?qū)ο蟮囊恍┲R,包括:

  • 類的定義尚镰、繼承
  • 使用 __mro__ 屬性查看某類的方法調(diào)用序列
  • 私有屬性和私有方法阀圾,以及如何訪問私有屬性和私有方法
  • 對象的 __del__ 方法以及查看對象引用計數(shù)的 getrefcount 方法
  • 子類中調(diào)用父類被覆寫的方法
  • 多態(tài)的實(shí)現(xiàn)以及其本質(zhì)就是繼承和方法覆寫
  • 類屬性和類方法
  • 類對象和實(shí)例對象,以及他們之間的關(guān)系
  • __new__ 方法的作用钓猬,以及基于該方法實(shí)現(xiàn)單例模式

完。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者

  • 序言:七十年代末撩独,一起剝皮案震驚了整個濱河市敞曹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌综膀,老刑警劉巖澳迫,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異剧劝,居然都是意外死亡橄登,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進(jìn)店門讥此,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拢锹,“玉大人,你說我怎么就攤上這事萄喳∽湮龋” “怎么了?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵他巨,是天一觀的道長充坑。 經(jīng)常有香客問我,道長染突,這世上最難降的妖魔是什么捻爷? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮份企,結(jié)果婚禮上也榄,老公的妹妹穿的比我還像新娘。我一直安慰自己司志,他們只是感情好手蝎,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布榕莺。 她就那樣靜靜地躺著,像睡著了一般棵介。 火紅的嫁衣襯著肌膚如雪钉鸯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天邮辽,我揣著相機(jī)與錄音唠雕,去河邊找鬼。 笑死吨述,一個胖子當(dāng)著我的面吹牛岩睁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播揣云,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼捕儒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了邓夕?” 一聲冷哼從身側(cè)響起刘莹,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎焚刚,沒想到半個月后点弯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡矿咕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年抢肛,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碳柱。...
    茶點(diǎn)故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡捡絮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出莲镣,到底是詐尸還是另有隱情锦援,我是刑警寧澤,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布剥悟,位于F島的核電站灵寺,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏区岗。R本人自食惡果不足惜略板,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望慈缔。 院中可真熱鬧叮称,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至挠蛉,卻和暖如春祭示,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背谴古。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工质涛, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人掰担。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓汇陆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親带饱。 傳聞我的和親對象是個殘疾皇子毡代,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評論 2 359

推薦閱讀更多精彩內(nèi)容