Python學習之面向物件(封裝、繼承、多型)
面向物件
關於面向物件大家應該很熟知,即使說不出他的概念,但是至少記住他的三大特徵:封裝、繼承、多型。
封裝
所謂封裝,也就是把客觀事物封裝成抽象的類,並且類可以把自己的資料和方法只讓可信的類或者物件操作,對不可信的進行資訊隱藏。
類的定義
基本形式:
class ClassName(object):
pass
- class定義類的關鍵字.
- ClassName類名,類名的每個單詞的首字母大寫(駝峰規則).
- object是父類名,object是一切類的基類。在python3中如果繼承類是基類可以省略不寫。
- pass 是類身體,由變數(類變數、例項變數)、方法組成(例項方法、靜態方法、類方法)
示例:
class Animal():
eye=2 #類變數
def __init__(self,name):
self.animalName=name#例項變數
print("我是初始化方法,也可以叫我構造器")
def move(self):
print("我是例項方法")
@staticmethod
def eat(food):
Animal.eye
print("我是靜態方法:",Animal.eye)
@classmethod
def run(cls):
print("我是類方法:",cls.eye)
定義一個Animal類:
- 類變數:類變數在整個例項化的物件中是公用的。類變數定義在類中且在函式體之外。類變數通常不作為例項變數使用。
- 例項變數:定義在方法中的變數,屬於例項。
- 初始化方法
__init__
:被稱為類的建構函式或初始化方法,當建立了這個類的例項時就會呼叫該方法。 - 例項方法:類的例項化物件呼叫,
- self:代表類的例項,而非類本身,self 在定義例項方法時是必須有的,雖然在呼叫時不必傳入相應的引數。
- 靜態方法:用@staticmethod修飾,類可以不用例項化就可以呼叫該方法,當然也可以例項化呼叫,不強制要求傳遞引數。
- 類方法:用@classmethod修飾,不需要 self 引數,但第一個引數需要是表示自身類的 cls 引數。
類的例項化
例項化
沒有new關鍵字,只需要一個例項名來接收類,並且賦上需要初始的值
dog=Animal("阿黃")
cat=Animal("喵喵")
呼叫方法:
例項方法的呼叫:
當例項呼叫時,預設將當前例項傳進去。
類呼叫時,只能以 類名.method(類例項) 形式呼叫。
dog.move()
cat.move()
靜態方法的呼叫:例項和類呼叫,沒有預設的引數傳進函式
Anmial.eat()
dog.eat()
類方法的呼叫:
當例項呼叫classmethod方法時,預設會把當前例項所對應的類傳進去,
當類呼叫classmethod方法時,預設把此類傳進去。
Anmial.run()
dog.eat()
至於__init__()
,是在例項化物件時自動呼叫
呼叫變數:
print(dog.eye)#2
print(dog.animalName)#阿黃
print(cat.eye)#2
print(cat.animalName)#喵喵
一個完整的示例:
class Animal():
eye=2 #類變數
def __init__(self,name):
self.animalName=name#例項變數
print("我是初始化方法,也可以叫我構造器")
def move(self,way):
print("我是例項方法:","%s在%s移動"%(self.animalName,way))
@staticmethod
def eat(self,food):
Animal.eye
print("我是靜態方法:","%s吃%s"%(self.animalName,food))
@classmethod
def run(cls,self):
print("我是類方法:","%s有%s隻眼睛"%(self.animalName,cls.eye))
dog=Animal("阿黃")#我是初始化方法,也可以叫我構造器
cat=Animal("喵喵")#我是初始化方法,也可以叫我構造器
dog.move("馬路上")#我是例項方法: 阿黃在馬路上移動
Animal.eat(dog,"骨頭")#我是靜態方法: 阿黃吃骨頭
Animal.eat(cat,"小魚")#我是靜態方法: 喵喵吃小魚
Animal.run(dog)#我是類方法: 阿黃有2隻眼睛
Animal.run(cat)#我是類方法: 喵喵有2隻眼睛
類的私有屬性和私有方法
對於python中的類屬性,或者方法,可以通過雙下劃線_或者單下劃線來實現一定程度的私有化。
_:以單下劃線開頭只能允許其本身與子類進行訪問,(對於例項只是隱藏起來了,可訪問,可修改)。(protected)
__:以雙下劃線開頭只能允許類本身呼叫,類的例項不能直接呼叫。(private)
python 的私有不是真正的私有,只是約定俗稱的規則。即使私有了我們依然可以通過
dog._Animal__leg(但是dog._Animal_a 不可以訪問)來訪問私有變數__leg。當然設計者也可以在類中設定方法讓訪問者操作私有屬性。
示例:
class Animal():
__leg="四條腿"
_eye="兩隻眼睛"
def __init__(self,name):
self.__name=name
def get__leg(self):
return self.__leg
def set__leg(self,leg):
self.__leg=leg
def __play(self):
print("%s在玩"%self.__name)
dog=Animal("小狗")
print(dog._eye)#兩隻眼睛
print(dog.get__leg())#四條腿
print(dog._Animal__leg)#四條腿
#print(dog._Animal_eye)#AttributeError: 'Animal' object has no attribute '_Animal_eye'
dog.set__leg("三條腿")
print(dog.get__leg())#三條腿
print(dog._Animal__name)#小狗
dog._Animal__play()#小狗在玩
print(dog.__dict__)#{'_Animal__name': '小狗', '_Animal__leg': '三條腿'}
print(dir(dog))#dir檢視類的所有屬性和方法
['_Animal__leg', '_Animal__name', '_Animal__play', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_eye', 'get__leg', 'set__leg']
從上述也可以看出__leg ,在記憶體中是_Animal__leg
注:前後都有雙下劃線的是python的特殊方法如__init__(), __del__()
等。
繼承:
繼承是指這樣一種能力:它可以使用現有類的所有功能,並在無需重新編寫原來的類的情況下對這些功能進行擴充套件。
python的繼承分單繼承和多繼承。
單繼承:
示例:
class man():
__sing="唱歌"
_dance="跳舞"
def __init__(self,name,age):
self.name=name
self.age=age
def say(self):
print("我是%s,我%s歲。"%(self.name,self.age))
class son(man):
def __init__(self,sex,name,age):
self.sex=sex
super().__init__(name,age)
class girl(man):
pass
son=son("男","張三",12)
son.say()#我是張三,我12歲。
girl=girl("小紅",14)
girl.say()#我是小紅,我14歲。
print(sorted(dir(son),reverse=True))
#['sex', 'say', 'name', 'age', '_man__sing', '_dance', '__weakref__', '__subclasshook__', '__str__', '__sizeof__', '__setattr__', '__repr__', '__reduce_ex__', '__reduce__', '__new__', '__ne__', '__module__', '__lt__', '__le__', '__init_subclass__', '__init__', '__hash__', '__gt__', '__getattribute__', '__ge__', '__format__', '__eq__', '__doc__', '__dir__', '__dict__', '__delattr__', '__class__']
son,girl都繼承了man的屬性和方法,從dir(son)可看出,__sing沒有繼承,_dance繼承了。
多繼承:
我舉了一個祖孫三代的例子:
爺爺有一個名字,會說話,會踢足球;父親繼承了爺爺,但是會跑,並且重寫了play方法會打籃球;小朋友是父親的兒子,繼承了父親,自然也繼承了爺爺,但是他並不會打籃球,他會踢足球。問題來了,爺爺和父親都有play(),小朋友到底繼承了誰的play()?
首先請看示例:
class Grandpa():
def __init__(self,name):
self.name=name
def say(self):
print("%s會說話"%self.name)
def play(self):
print("%s會踢足球"%self.name)
class Father(Grandpa):
def run(self):
print("%s會跑了"%self.name)
def play(self):
print("%s會打籃球"%self.name)
class Child(Father,Grandpa):
def play(self):
#super(Child, self).play()
super(Father, self).play()
print(Child.__mro__)#(<class '__main__.Child'>, <class '__main__.Father'>, <class '__main__.Grandpa'>, <class 'object'>)
c=Child("小朋友")
c.run()#小朋友會跑了
c.say()#小朋友會說話
c.play()#小朋友會踢足球
我們可以通過Child.__mro__
列印Child的繼承路線:(請記住這個繼承順序,不能亂)
(<class '__main__.Child'>, <class '__main__.Father'>, <class '__main__.Grandpa'>, <class 'object'>)
首先小朋友是他自己,其次他是Father的孩子,其次是Granpa的孫子,再其次他的祖先是object,這個繼承順序不能亂,就像祖孫三代的關係不能亂。
預設小盆友是繼承了父親的打籃球,但是我現在希望的是小盆友是繼承爺爺的踢足球,那就要重寫play方法,修改繼承順序:
super(Father, self).play()
#super裡寫的Father並不是繼承Father,而是Father的上一輩Grandpa
#預設是這個樣子的
super(Child, self).play()
其實可以直接用類名呼叫相應的play方法(這樣child就既會踢足球又會打籃球了)如:
def play(self):
Grandpa.play(self)
Father.play(self)
當然繼承裡也不能這樣寫Child(Grandpa,Father)
因為這樣寫的繼承順序是:
(<class '__main__.Child'>, <class '__main__.Grandpa'>, <class '__main__.Father'>,<class 'object'>)
系統會報錯:
TypeError: Cannot create a consistent method resolution
order (MRO) for bases Grandpa, Father
假如Father又有了一個例項屬性age(其他都省略,我們只討論init())
class Grandpa():
def __init__(self,name):
self.name=name
class Father(Grandpa):
def __init__(self,age,name):
self.age=age
super().__init__(name)
class Child(Father,Grandpa):
'''def __init__(self,age,name):
super().__init__(age)
super(Father, self).__init__(name)'''
def sing(self):
print("我叫%s,我今年%s歲"%(self.name,self.age))
c=Child(12,"xfy")
c.sing()
f=Father(12,"f")
print(f.name)
Father自己有了__init__()
,重寫了Granpa的__init__()
,所以要呼叫Grandpa的__init__()
,這樣Child就預設繼承了Father的__init__()
,他就有了name和age。
多型:
所謂多型就是指一個類例項的相同方法在不同情形有不同表現形式。多型機制使具有不同內部結構的物件可以共享相同的外部介面。這意味著,雖然針對不同物件的具體操作不同,但通過一個公共的類,它們(那些操作)可以通過相同的方式予以呼叫。
python的多型並沒什麼好講的
當派生類,重寫了基類的方法時就實現了多型性。(子類重寫父類方法)
python的封裝、繼承、多型就先告一段落,有任何疑問都可以留言評論。