1. 程式人生 > 實用技巧 >面向物件三大特性 封裝 繼承 多型

面向物件三大特性 封裝 繼承 多型

1 繼承

1:定義

繼承描敘的是兩個類之間的關係,一個類可以直接使用另一個類中已定義的方法和屬性;
被繼承的稱之為父類或基類,繼承父類的類稱之為子類;

在python3中建立類時必然繼承另一個類,如果沒有顯式的指定父類,則預設繼承object類; object是根類 所有類都直接或間接的繼承object

2 繼承的作用

1.減少程式碼重複

2.為多型提供必要的支援

3 繼承的使用

1 先抽象在繼承

# 抽取老師和學生的相同內容 形成一個新的類,作為它們的公共父類
class Person:
    def __init__(self,name,gender,age):
        self.name = name
        self.gender = gender
        self.age = age
    def say_hi(self):
        print("hi my name is %s age is %s gender is %s" % (self.name,self.age,self.gender))
class Teacher(Person):	#指定Teacher類繼承Person類
    pass
class Student(Person):  #指定Student類繼承Person類
    pass

#建立兩個物件
t1 = Teacher("Jack","man",20)
t1.say_hi()
s1 = Student("Maria","woman",20)
s1.say_hi()

2 派生和覆蓋

派生

當父類提供的屬性無法完全滿足子類的需求時,子類可以增加自己的屬性或非法,或者覆蓋父類已經存在的屬性,此時子類稱之為父類的派生類;

覆蓋  

在子類中如果出現於父類相同的屬性名稱時,根據查詢順序,優先使用子類中的屬性,這種行為也稱為`覆蓋`

  

# 抽取老師和學生的相同內容 形成一個新的類,作為它們的公共父類
class Person:
    def __init__(self,name,gender,age):
        self.name = name
        self.gender = gender
        self.age = age
    def say_hi(self):
        print("my name is %s age is %s gender is %s" % (self.name,self.age,self.gender))
class Teacher(Person):	#指定Teacher類繼承Person類
    # Teacher類從Person類中繼承到了say_hi方法 但是,老師打招呼時應當說出自己的職業是老師,所以需要
    # 定義自己的不同的實現方式
    def say_hi(self):
        print("hi i am a Teacher")
        #print("my name is %s age is %s gender is %s" % (self.name,self.age,self.gender))
        #上一行程式碼與父類中完全相同,可以直接呼叫父類提供的方法
        Person.say_hi(self)
# 建立Teacher物件
t1 = Teacher("Jack","man",20)
t1.say_hi()
#輸出 hi i am a Teacher
#     my name is Jack age is 20 gender is man

  

3 子類中重用父類的方法(重點使用super())

很多情況下 子類中的程式碼與父類中僅有小部分不同,卻不得不在子類定義新的方法,這時候可以在子類中呼叫父類已有的方法,來完成大部分工作,子類僅需編寫一小部分與父類不同的程式碼即可

在子類中有兩種方式可以重用父類中的程式碼

1.使用類名直接呼叫 ,該方式與繼承沒有關係,即時沒有繼承關係,也可以呼叫

2.使用super()

class Vehicle: #定義交通工具類
     Country='China'
     def __init__(self,name,speed,load,power):
         self.name=name
         self.speed=speed
         self.load=load
         self.power=power

     def run(self):
         print('開動啦...')

class Subway(Vehicle): #地鐵
    def __init__(self,name,speed,load,power,line):
        #super(Subway,self) 就相當於例項本身 在python3中super()等同於super(Subway,self)
        super().__init__(name,speed,load,power)
        self.line=line

    def run(self):
        print('地鐵%s號線歡迎您' %self.line)
        super(Subway,self).run()

class Mobike(Vehicle):#摩拜單車
    pass

line13=Subway('中國地鐵','180m/s','1000人/箱','電',13)
line13.run()
'''
地鐵13號線歡迎您
開動啦...
'''

  

4 經典類與新式類

那什麼是經典類,什麼是新式類呢?

經典類和新式類的主要區別就是類的繼承的方式 ,經典類遵循深度優先的規則,新式類遵循廣度優先的規則。至於什麼是深度優先什麼是廣度優先,可以看如下示例:

 

# Author:Zhang Zhao
class A(object):
    def __init__(self):
        print('A')
class B(A):
    pass
    # def __init__(self):
    #     print('B')
class C(A):
    def __init__(self):
        print('C')
class D(B,C):
    pass
    # def __init__(self):
    #     print('D')
r1 = D()

 

在新式類中,D是繼承B和C的,按照順序,首先去找B,如果在B裡面能找到例項化物件,便繼承B,不再往別的地方尋找,如果沒有,就會接著找C,而不是找B的父親A!

但是在經典類中,如果B中找不到,它會優先考慮B的父親A,而不是C。

在python3中,都是遵循廣度優先的規則,在python2.7以前,應該是遵循深度優先的的規則。兩種規則沒有優劣之分

 

即使沒有直接繼承關係,super仍然會按照mro繼續往後查詢

而第一種方式明確指定了要到哪一個類中去查詢,找不到則直接丟擲異常

#A沒有繼承B,但是A內super會基於C.mro()繼續往後找
class A:
    def test(self):
        super().test()
class B:
    def test(self):
        print('from B')
class C(A,B):
    pass

c=C()
c.test() #列印結果:from B


print(C.mro())
#[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]

 *當你使用super()函式時,Python會在MRO列表上繼續搜尋下一個類。如果每個重定義的方法統一使用super()並只調用它一次,那麼控制流最終會遍歷完整個MRO列表,每個方法也只會被呼叫一次(注意注意注意:使用super呼叫的所有屬性,都是從MRO列表當前的位置往後找,千萬不要通過看程式碼去找繼承關係,一定要看MRO列表) 

5 繼承的順序

對於你定義的每一個類,python通過一個演算法算出一個查詢順序存放在(MRO)列表中,這個MRO列表就是一個簡單的所有基類的線性順序列表,例如:

F.mro() #等同於F.__mro__
[<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

為了實現繼承,python會在MRO列表上從左到右開始查詢基類,直到找到第一個匹配這個屬性的類為止。 而這個MRO列表的構造是通過一個C3線性化演算法來實現的。不需要深究這個演算法的原理,它實際上就是合併所有父類的MRO列表並遵循如下三條準則:  

1.子類會先於父類被檢查

2.多個父類會根據它們在列表中的順序被檢查

3.如果對下一個類存在兩個合法的選擇,選擇第一個父類