1. 程式人生 > >我的Python成長之路--Day25--面向物件的三大特性介紹01(繼承和派生)

我的Python成長之路--Day25--面向物件的三大特性介紹01(繼承和派生)

面向物件一共有三個重要的特性,分別是:繼承、多型和封裝.今天來具體介紹一下繼承相關的知識點

1.什麼是繼承? 在程式中繼承是一種新建類的方式,新建立的類稱之為子類/派生類,被繼承的類稱之為父類/基類/超類

繼承描述的是一種遺傳關係,子類可以重用父類的屬性

2.為什麼要用繼承 減少類與類之間的程式碼冗餘的問題

3.怎麼用繼承? 先抽象再繼承,意思就是在類與類之間先抽離出相似的部分,然後建立一個父類,將相似的部分放進父類中,再在子類中進行繼承

在python2和python3中在繼承上的區別:

新式類:但凡繼承object類的子類,以及該子類的子子類,...都稱之為新式類經典類:沒有繼承object類的子類,以及該子類的子子類,...都稱之為經典類 在python3中不管怎麼定義類都是新式類,都是預設繼承object類的,而在python2中,如果明確寫上class Foo(object),這樣的類就是新式類,class Foo()這樣的類則稱為經典類,以後用的類一般都是新式類,但是會有程式設計師在python3中class Foo(object)這樣定義類,這樣寫不是習慣的問題,而是這樣寫,即使拿到python2中也可以按照新式類去執行

提示:如果沒有指定基類,python的類會預設繼承object類,object是所有python類的基類,它提供了一些常見方法(如__str__)的實現  

>>> ParentClass1.__bases__
(<class 'object'>,)
>>> ParentClass2.__bases__
(<class 'object'>,)

在python中繼承可以分為單繼承和多繼承:單繼承是指一個子類只繼承一個父類,多繼承指的是一個子類繼承多個父類,在繼承多個父類的時候,屬性的查詢順序要重點注意,在後邊會詳細介紹.

單繼承:

class Parent1(object):   
    pass

class Parent2:
    pass

class Sub1(Parent1):         單繼承
    pass

class Sub2(Parent1,Parent2): 多繼承
    pass
                            __bases__檢視子類繼承的父類情況:(以元祖的形式顯示出來)
print(Sub1.__bases__)      (<class '__main__.Parent1'>,)
print(Sub2.__bases__)      (<class '__main__.Parent1'>, <class '__main__.Parent2'>)

print(Parent1.__bases__)   (<class 'object'>,)
print(Parent2.__bases__)   (<class 'object'>,)    python3中都是預設繼承object的新式類
  

繼承與抽象(先抽象再繼承)

繼承描述的是子類與父類之間的關係,是一種什麼是什麼的關係。要找出這種關係,必須先抽象再繼承

抽象即抽取類似或者說比較像的部分。

抽象分成兩個層次: 

1.將奧巴馬和梅西這倆物件比較像的部分抽取成類; 

2.將人,豬,狗這三個類比較像的部分抽取成父類。

抽象最主要的作用是劃分類別(可以隔離關注點,降低複雜度)

繼承:是基於抽象的結果,通過程式語言去實現它,肯定是先經歷抽象這個過程,才能通過繼承的方式去表達出抽象的結構

抽象只是分析和設計的過程中,一個動作或者說一種技巧,通過抽象可以得到類

基於繼承解決類與類程式碼冗餘問題:

在兩個子類中,__init__函式所定義出來的內容是一樣的,為了減少程式碼冗餘,將其抽離出來,新建一個父類將相同功能的程式碼放到父類中,然後再子類中繼承父類,這樣就可以重用父類中的功能了

class OldboyPeople:                父類
    school = 'Oldboy'
    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender

# print(OldboyPeople.__init__)

class OldboyStudent(OldboyPeople):   子類
    # def __init__(self, name, age, gender):     
        # self.name = name
        # self.age = age
        # self.gender = gender

    def choose_course(self):
        print('%s is choosing course' %self.name)

class OldboyTeacher(OldboyPeople):   子類
    # def __init__(self, name, age, gender):
        # self.name = name
        # self.age = age
        # self.gender = gender
        
    def score(self,stu,num):
        stu.num=num
        print('老師%s給學生%s打分%s' %(self.name,stu.name,num))

stu=OldboyStudent('kevin',38,'male')   #__init___(stu1,'kevin',38,'male')
print(stu.__dict__)   可以完成初始化{'name': 'kevin', 'age': 38, 'gender': 'male'}

tea=OldboyTeacher('egon',18,'male')    #__init___(tea,'egon',18,'male')
print(tea.__dict__)   {'name': 'egon', 'age': 18, 'gender': 'male' }  
                      
                    可以使用到父類中的功能:
print(stu.school)   Oldboy
print(tea.school)   Oldboy

單繼承背景下屬性的查詢順序:

在單繼承背景下,無論是新式類還是經典類屬性查詢順序都一樣 先從obj本身找->在到產生obj的類中找->父類->...            注意:所尋找的屬性,只要是obj本身有的,都以本身的為主,本身沒有的再按照上面的順序一層一層的往下尋找

class Foo:     def f1(self):         print('Foo.f1')

    def f2(self):         print('Foo.f2')         self.f1()     #obj.f1()

class Bar(Foo):     def f1(self):         print('Bar.f1')

obj=Bar()   obj.f2()     結果:Foo.f2                         Bar.f1     (在產生obj的類Bar中是有f1的,要以Bar中的為主)

在多繼承背景下的屬性查詢順序

如果一個子類繼承了多個分支,但是多個分支沒有匯聚到一個非object類,無論是新式類還是經典類屬性查詢順序都一樣: 會按照從左到右的順序一個分支一個分支的查詢下去,按照下圖所示流程順序查詢

程式碼形式:  

class E:
    xxx='E'
    pass

class F:
    xxx='F'
    pass

class B(E):
    xxx='B'
    pass

class C(F):
    xxx='C'
    pass

class D:
    xxx='D'
    pass

class A(B,C,D):
    xxx='A'
    pass

obj=A()
# obj.xxx=111  (有就用物件自己的)
print(obj.xxx)

在多繼承背景下的菱形繼承問題

如果一個子類繼承了多個分支,但是多個分支最終匯聚到一個非object類(菱形繼承問題) 新式類:廣度優先查詢:obj->A->B->E->C->F->D->G->object 經典類:深度優先查詢:obj->A->B->E->G->C->F->D 這兩種繼承順序都為菱形繼承.  

class A(object):
    def test(self):
        print('from A')

class B(A):
    def test(self):
        print('from B')

class C(A):
    def test(self):
        print('from C')

class D(B):
    def test(self):
        print('from D')

class E(C):
    def test(self):
        print('from E')

class F(D,E):
    # def test(self):
    #     print('from F')
    pass
f1=F()
f1.test()
print(F.__mro__) #只有新式才有這個屬性可以檢視線性列表,經典類沒有這個屬性

#新式類繼承順序:F->D->B->E->C->A
#經典類繼承順序:F->D->B->A->E->C
#python3中統一都是新式類
#pyhon2中才分新式類與經典類

繼承順序

繼承原理:

python到底是如何實現繼承的,對於你定義的每一個類,python會計算出一個方法解析順序(MRO)列表,這個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.如果對下一個類存在兩個合法的選擇,選擇第一個父類

派生:

當然子類也可以新增自己新的屬性或者在自己這裡重新定義這些屬性(不會影響到父類),需要注意的是,一旦重新定義了自己的屬性且與父類重名,那麼呼叫新增的屬性時,就以自己為準了。

class Riven(Hero):
    camp='Noxus'   也可以定義新的特徵,不會影響到父類
    def attack(self,enemy): #在自己這裡定義新的attack,不再使用父類的attack,且不會影響父類
        print('from riven')
    def fly(self): #在自己這裡定義新的fly,不會影響父類
        print('%s is flying' %self.nickname)

在子類中,新建的重名的函式屬性,在編輯函式內功能的時候,有可能需要重用父類中重名的那個函式功能,應該是用呼叫普通函式的方式,即:類名.func(),此時就與呼叫普通函式無異了,因此即便是self引數也要為其傳值

class Riven(Hero):
    camp='Noxus'
    def __init__(self,nickname,aggressivity,life_value,skin):
        Hero.__init__(self,nickname,aggressivity,life_value) #呼叫父類功能

        self.skin=skin          #新屬性

    def attack(self,enemy):     #在自己這裡定義新的attack,不再使用父類的attack,且不會影響父類
        Hero.attack(self,enemy) #呼叫功能
        print('from riven')

    def fly(self):              #在自己這裡定義新的功能
        print('%s is flying' %self.nickname)

r1=Riven('銳雯雯',57,200,'兔女郎')
r1.fly()
print(r1.skin)

'''
執行結果
銳雯雯 is flying
兔女郎

'''

子類中呼叫父類的兩種方法:

之前簡單介紹了多個子類繼承父類的方法,但是在實際情況中,並不是每個子類的相似內容都是像例子一樣完全相同的,當其中一個子類中出現了不僅僅需要父類的和其他子類相同的屬相,它本身還需要其他的屬性的時候,這個時候這個子類就需要自己再將自己需要的獨有的屬性定義出來,在這裡介紹兩種方法:

在子類派生出的新功能中如何重用父類的功能: 方式一: 指名道姓地訪問某一個類中的函式,與繼承無關             在子類中的__init__下面知名道姓的直接訪問父類的__init__供自己使用,再加上自己的獨有的屬性

#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'

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):
        Vehicle.__init__(self,name,speed,load,power)
        self.line=line

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

line13=Subway('中國地鐵','180m/s','1000人/箱','電',13)
line13.run()

方式二: super(OldboyTeacher,self), 在python3中super可以不傳引數,呼叫該函式會得到一個特殊的物件,該物件是專門用來訪問父類中屬性, 強調:super會嚴格參照類的MRO列表依次查詢屬性

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

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()

注意: 這兩種在子類中重用父類功能的方法使用哪種都可以,但是不要混用!!!

關於MRO列表的