天天看點

19、面向對象(繼承)

19.1、繼承介紹:

1、什麼是繼承:

繼承是一種建立新類的方式,建立的類可以繼承一個或多個父類(python支援多繼承),父類又可稱為基類或超類,

建立的類稱為派生類或子類。子類會“遺傳”父類的屬性,進而解決代碼重用問題;

在開發程式的過程中,如果我們定義了一個類A,然後又想建立立另外一個類B,但是類B的大部分内容與類A的相同

時我們不可能從頭開始寫一個類B,這就用到了類的繼承的概念。通過繼承的方式建立類B,讓B繼承A,B會‘遺傳’

A的所有屬性(資料屬性和函數屬性),實作代碼的重用;

2、python中類的繼承分類:

(1)定義父類:

class ParentClass1:

pass

class ParentClass2:

pass

(2)單繼承:

class SubClass1(ParentClass1):

pass

# 單繼承,父類是ParentClass1,子類是SubClass;

(3)多繼承:

class SubClass2(ParentClass1,ParentClass2):

pass

# python支援多繼承,用逗号分隔開多個繼承的類;

3、經典類與新式類:

(1)隻有在python2中才分新式類和經典類,python3中統一都是新式類

(2)在python2中,沒有顯式的繼承object類的類,以及該類的子類,都是經典類

(3)在python2中,顯式地聲明繼承object的類,以及該類的子類,都是新式類

(4)在python3中,無論是否繼承object,都預設繼承object,即python3中所有類均為新式類

4、繼承的順序:

(1)在 Java 和 C# 中子類隻能繼承一個父類,而Python中子類可以同時繼承多個父類,如 A(B,C,D)

如果繼承關系為非菱形結構,則會按照先找B這一條分支,然後再找C這一條分支,最後找D這一條

分支的順序直到找到我們想要的屬性,如果繼承關系為菱形結構,那麼屬性的查找方式有兩種,分

别是:深度優先和廣度優先;

(2)經典類(深度優先):

19、面向對象(繼承)

(3)新式類(廣度優先):

19、面向對象(繼承)

5、繼承原理(python如何實作的繼承):

(1)python到底是如何實作繼承的,對于你定義的每一個類,python會計算出一個方法解析順序(MRO)清單,這個MRO清單就是一個簡單

的所有基類的線性順序清單。

例如:>>> F.mro() 就等同于 F.__mro__,得到的結果如下:

(<class '__main__.F'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)

(2)為了實作繼承,python 會在 MRO 清單上從左到右開始查找基類,直到找到第一個比對這個屬性的類為止。而這個 MRO 清單的構造是通過一個 C3 線性化

算法來實作的,我們不去深究這個算法的數學原理,它實際上就是合并所有父類的 MRO 清單并遵循如下三條準則:

1)子類會先于父類被檢查;

2)多個父類會根據它們在清單中的順序被檢查;

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

19.2、繼承示例:

class Dad:

# 父類
    money=1000000
    def __init__(self,name):
        print('father_class')
        self.name=name
    def hit_son(self):
        print('%s 正在打兒子' %self.name)

class Son(Dad):
    # 子類
    money = 10000000
    def __init__(self,name,age):
        self.name=name
        self.age=age

    def hit_son(self):
        print('son_class')


# print(Dad.__dict__)
# print(Son.__dict__)
s1=Son('son',22)
s1.hit_son()
print(s1.money)
print(Dad.money)
# son_class
# 10000000
# 1000000
# 子類繼承父類後擁有父類的資料屬性和函數屬性,使用子類生成
# 對象後調用子類的資料屬性和函數屬性,如果在子類中找不到回到父類中
# 尋找(若果子類中的資料屬性或函數屬性和父類相同則優先使用
# 子類中的)      

19.3、類的繼承順序示例:

1、繼承類圖:

19、面向對象(繼承)

2、新式類繼承順序:

class A:
    def test(self):
        print('A')
    pass

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

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

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

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

class F(E,D):
    def test(self):
        print('F')
    pass
f1=F()
f1.test()   
print(F.__mro__)
# 該方法隻有新式類才有
#新式類:F->E->C->D->B->A-object      

2、經典類繼承順序:

經典類:F->E->C->A->D->B

19.4、接口繼承:

1、什麼是接口:

java中用 interface 關鍵字來聲明接口類;

例如:hi boy,給我開個查詢接口。。。此時的接口指的是:自己提供給使用者來調用自己功能的方式\方法\入口;

2、為何要用接口:

(1)接口提取了一群類共同的函數,可以把接口當做一個函數的集合,然後讓子類去實作接口中的函數。

(2)使用接口的意義在于歸一化,歸一化就是隻要是基于同一個接口實作的類,那麼所有的這些類産生的對象在使

用時,從用法上來說都一樣。

(3)歸一化的好處在于:

1)歸一化讓使用者無需關心對象的類是什麼,隻需要的知道這些對象都具備某些功能就可以了,這極大地降低了使

用者的使用難度。

2)歸一化使得高層的外部使用者可以不加區分的處理所有接口相容的對象集合;

3)舉例:

比如:linux的泛檔案概念,所有東西都可以當檔案處理,不必關心它是記憶體、磁盤、網絡還是螢幕(當然,對

底層設計者,當然也可以區分出“字元裝置”和“塊裝置”,然後做出針對性的設計:細緻到什麼程度,視需求而定)。

比如:我們有一個汽車接口,裡面定義了汽車所有的功能,然後由本田汽車的類,奧迪汽車的類,大衆汽車的類,

他們都實作了汽車接口,這樣就好辦了,大家隻需要學會了怎麼開汽車,那麼無論是本田,還是奧迪,還是大衆我們都

會開了,開的時候根本無需關心我開的是哪一類車,操作手法(函數調用)都一樣。

(4)在python中根本就沒有一個叫做 interface 的關鍵字,如果非要去模仿接口的概念,也可以使用繼承:

1)繼承基類的方法,并且做出自己的改變或者擴充(代碼重用):實踐中,繼承的這種用途意義并不很大,甚至常常是

有害的。因為它使得子類與基類出現強耦合。

2)聲明某個子類相容于某基類,定義一個接口類(模仿java的Interface),接口類中定義了一些接口名(就是函數名)

且并未實作接口的功能,子類繼承接口類,并且實作接口中的功能;

3)在 python 中使用繼承其實并沒有起到接口的作用,子類完全可以不用去實作接口 ,這就用到了抽象類。

3、使用抽象類實作接口功能:

(1)什麼是抽象類:

與java一樣,python也有抽象類的概念,但是同樣需要借助子產品實作,抽象類是一個特殊的類,它的特殊之處在于隻能被

繼承,不能被執行個體化;

(2)為什麼要用抽象類:

1)說明:

比如我們有香蕉的類,有蘋果的類,有桃子的類,從這些類抽取相同的内容就是水果這個抽象的類,你吃水果時,要麼是吃一個具體的

香蕉,要麼是吃一個具體的桃子。。。。。。你永遠無法吃到一個叫做水果的東西。

2)從設計角度去看:如果說類是從一堆對象中抽取相同的内容而來的,那麼抽象類就是從一堆類中抽取相同的内容而來的,内容包括資料屬性和函數屬性;

3)從實作角度來看:抽象類與普通類的不同之處在于,抽象類中隻能有抽象方法(沒有實作功能),該類不能被執行個體化,隻能被繼承,

且子類必須實作抽象方法,這一點與接口有點類似,但其實是不同的;

(3)抽象類與接口:

抽象類的本質還是類,指的是一組類的相似性,包括資料屬性(如all_type)和函數屬性(如read、write),而接口隻強調函數屬性的相似性。

抽象類是一個介于類和接口之間的一個概念,同時具備類和接口的部分特性,可以用來實作歸一化設計,可以了解接口是一個特殊的抽象類;

(4)抽象類實作接口功能示例:

import abc
class All_file(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def read(self):
        pass

    @abc.abstractmethod
    def write(self):
        pass

class Disk(All_file):
    def read(self):
        print('disk read')

    def write(self):
        print('disk write')

class Cdrom(All_file):
    def read(self):
        print('cdrom read')

    def write(self):
        print('cdrom write')


class Mem(All_file):
    def read(self):
        print('mem read')

    def write(self):
        print('mem write')

m1=Mem()
m1.read()
m1.write()
# mem read
# mem write

19.5、子類中調用父類中的方法:
1、示例:
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)
           # super().__init__(name,speed,load,power)
           # super(__class__,self).__init__(name,speed,load,power)
           super(Subway,self).__init__(name,speed,load,power)
           self.line=line

        def show_info(self):
            print(self.name,self.speed,self.load,self.power,self.line)

        def run(self):
            # Vehicle.run(self)
            super(Subway,self).run()
            print('%s %s 線,開動啦' %(self.name,self.line))

line13=Subway('北京地鐵','10km/s',1000000000,'電',13)
line13.show_info()
line13.run()
print(line13.__class__)
# 北京地鐵 10km/s 1000000000 電 13
# 開動啦
# 北京地鐵 13 線,開動啦
# <class '__main__.Subway'>

# 當你使用 super() 函數時,Python 會在 MRO 清單上繼續搜尋下一個類,隻要每個重定義的方法統一使用 super() 并隻調用它一次,
# 那麼控制流最終會周遊完整個MRO清單,每個方法也隻會被調用一次;
# 注意:使用 super 調用的所有屬性都是從 MRO 清單目前的位置往後找,千萬不要通過看代碼去找繼承關系,一定要看MRO清單;