Python進階之物件導向

Stefan的程式碼小屋發表於2018-04-23

物件導向

關於物件導向大家應該很熟知,即使說不出他的概念,但是至少記住他的三大特徵:封裝、繼承、多型。

封裝

所謂封裝,也就是把客觀事物封裝成抽象的類,並且類可以把自己的資料和方法只讓可信的類或者物件操作,對不可信的進行資訊隱藏。

類的定義

基本形式:

class ClassName(object):
    pass
複製程式碼
  1. class定義類的關鍵字.
  2. ClassName類名,類名的每個單詞的首字母大寫(駝峰規則).
  3. object是父類名,object是一切類的基類。在python3中如果繼承類是基類可以省略不寫。
  4. 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()
複製程式碼

當然繼承裡也不能這樣寫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的封裝、繼承、多型就先告一段落,有任何疑問都可以留言評論。

相關文章