面向?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)行一些操作旭等。
示意圖如下:
現(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)單例模式
完。