21. 物件導向之繼承

hbutmeng發表於2024-08-16

1. 概念

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

python中類的繼承分為:單繼承和多繼承

2. 單繼承、多繼承、檢視繼承

2.1 單繼承

新類只繼承一個基類

class Animal:
    category = '動物'

    def eat(self):
        print('動物可以吃東西')

    def speak(self):
        print('動物可以發出聲音')


class Duck(Animal):  # 繼承了Animal類中的所有方法和屬性
    category = '鴨子'

    def swim(self):
        print('鴨子可以游泳')


obj = Duck()
obj.eat()  # 動物可以吃東西
print(obj.category)  # 鴨子   先在自己的屬性字典中找

2.2 多繼承、檢視繼承

新類繼承多個基類

class Animal:
    category = '動物'

    def eat(self):
        print('動物可以吃東西')

    def speak(self):
        print('動物可以發出聲音')


class Duck(Animal):  # 繼承了Animal類中的所有方法和屬性
    category = '鴨子'

    def swim(self):
        print('鴨子可以游泳')


class DonaldDuck():
    def play(self):
        print('Donald喜歡玩')


class SmallDuck(DonaldDuck, Duck):
    def eat(self):
        print('小鴨子喜歡吃東西')


obj = SmallDuck()
obj.eat()  # 小鴨子喜歡吃東西    先從自己的屬性字典中找
print(obj.category)  # 鴨子   基類DonaldDuck中沒有,去基類Duck中找
obj.play()  # Donald喜歡玩    去基類DonaldDuck中找

# _ _base_ _檢視從左到右繼承的第一個基類
# _ _bases_ _檢視所有繼承的基類
print(SmallDuck._ _bases_ _)  # (<class '__main__.DonaldDuck'>, <class '__main__.Duck'>)
print(SmallDuck._ _base_ _)  # <class '__main__.DonaldDuck'>

print(Animal._ _bases_ _)  # (<class 'object'>,)
print(Animal._ _base_ _)  # <class 'object'>

3. 經典類與新式類

只有在python2中才分新式類和經典類,python3中都是新式類

在python2中,沒有明顯地繼承object類,以及該類的子類,都是經典類

在python2中,明顯地宣告繼承object類,以及該類的子類,都是新式類

在python3中,都預設繼承object,即python3中所有類均為新式類

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

4. 屬性的查詢順序

4.1 不隱藏屬性

有了繼承關係,物件在查詢屬性時,先從物件自己的屬性字典中找,沒有則去基類中找

class Teacher:
    def f1(self):
        print('當前是Teacher的f1')

    def f2(self):  # (3)在基類中找到了f2方法
        print('當前是Teacher的f2')
        print(self)  # (8)檢視self的本質  <__main__.Student object at 0x000001BD9E42B9D0>
        # (4)self的本質是一個物件,因此這裡的self是stu1物件
        #    而stu1物件的屬性字典中有f1方法,該方法在產生物件的Student類中
        #    因此又回到了Student類中
        self.f1()


class Student(Teacher):  # (2)來到Student類中找f2方法,發現沒有,於是去基類Teacher中查詢
    def f1(self):  # (5)找到了f1方法
        print('當前是Student的f1')


stu1 = Student()  # 例項化得到一個物件stu1
stu1.f2()  # (1)stu1物件呼叫f2方法
# (6)因此列印的順序是'當前是Teacher的f2,'當前是Student的f1'
# 當前是Teacher的f2
# 當前是Student的f1



# (7)檢視stu1的本質
print(stu1)  # <__main__.Student object at 0x000001BD9E42B9D0>
# (9)根據步驟7和步驟8可以得出結論,Teacher類f2函式的self和物件stu1的記憶體地址一致

4.2 隱藏屬性

class Teacher:
    def __f1(self):  # 封裝之後函式名變成_Teacher__f1
        print('當前是Teacher的f1')

    def f2(self):  # (2)發現Student類中沒有,於是來到基類Teacher中找到f2
        print('當前是Teacher的f2')
        # print(self)  # (6)檢視self的本質<__main__.Student object at 0x000002BA5A73B9D0>
        self.__f1()  # (3)本質是stu1._Teacher__f1()

        # (4)發現物件stu1的屬性字典中並沒有_Teacher__f1,於是只能在類Teacher中找_Teacher__f1
        # print(stu1.__dict__)  # {}
        # print(Teacher.__dict__)  # '_Teacher__f1': <function Teacher.__f1 at 0x00000216C9D13370>, 'f2': <function Teacher.f2 at 0x00000216C9FE1750>,


class Student(Teacher):
    def __f1(self):  # 封裝之後函式名變成_Student__f1
        print('當前是Student的f1')


stu1 = Student()  # 例項化得到一個物件stu1
stu1.f2()  # (1)stu1物件呼叫f2方法
# (5)因此列印的順序是'當前是Teacher的f1',當前是Teacher的f2
# 當前是Teacher的f1'
# 當前是Teacher的f2


# (7)檢視stu1的本質
# print(stu1)  # <__main__.Student object at 0x000002BA5A73B9D0>
# (8)根據步驟6和步驟7可以得出結論,Teacher類f2函式的self和物件stu1的記憶體地址一致

4.3 總結

在沒有隱藏屬性的情況下,屬性的查詢順序是:

物件屬性字典---例項化得到該物件的類---基類---再上一級基類

在隱藏屬性的情況下,屬性的查詢順序是:

從當前執行該方法的類中去找封裝屬性---找不到就報錯

5. 繼承與抽象

抽象:抽取類似或者比較像的部分

哈蘭德、基利安—— 人

傑瑞、泰菲—— 鼠

斯派克、泰克—— 狗

人、鼠、狗—— 動物

繼承:基於抽象的結果,得到這些比較像的部分

動物—— 人、鼠、狗

人—— 哈蘭德、基利安

鼠—— 傑瑞、泰菲

狗—— 斯派克、泰克

6. 菱形繼承的屬性查詢

6.1 概念

菱形繼承屬性查詢方式有兩種,分別是深度優先和廣度優先,區分條件是新式類和經典類。

6.2 程式碼

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):
    pass
obj = F()
obj.test()  # from D

6.3 經典類:深度優先

深度優先,先不分開去找,先一個方向找到最頂層基類,如果沒找到則從另一個方向找

F--D--B--A E--C

6.4 新式類:廣度優先

廣度優先,先不找最頂層的基類,一個方向找不到去另一個方向找,最後找頂層基類

F--D--B E--C--A

6.5 檢視類的查詢順序

_ _mro_ _ 可以檢視類的查詢順序,但是這個方法只對新式類有效

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):
    pass

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

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

7. 派生

7.1 概念

派生是指子類在繼承基類的屬性後,衍生出自己的屬性,並且重寫基類的屬性

7.2 引入

class Person:
    school = 'MIT'

    def __init__(self, name, age, grade):
        self.name = name
        self.age = age
        self.grade = grade

    def lab(self):
        print('實驗')

class Student(Person):
    def __init__(self, course):
        self.course = course
    def lab(self):
        print('學生正在做實驗')

stu1 = Student(course='控制工程')
print(stu1.course)  # 控制工程
print(stu1.school)  # MIT
stu1.lab()  # 學生正在做實驗

# 找不到grade,原因是在子類中又重寫了基類_ _ init _ _的方法,導致了原本的init方法失效
print(stu1.grade)  # AttributeError: 'Student' object has no attribute 'grade'

7.3 子類派生屬性中繼承基類屬性的兩種方法

方法一:呼叫基類的函式

class Person:
    school = 'MIT'

    def __init__(self, name, age, grade):
        self.name = name
        self.age = age
        self.grade = grade

    def lab(self):
        print('實驗')

class Student(Person):
    def __init__(self, course, name, age, grade):
        Person.__init__(self, name, age, grade)  # 子類從基類中繼承屬性
        self.course = course

    def lab(self):
        print('學生正在做實驗')

stu1 = Student(name='lavigne', age=20, grade=2, course='control')
print(stu1.course)  # control
print(stu1.school)  # MIT

方法二:使用內建函式super

呼叫super()會得到一個特殊的物件,該物件專門用來引用基類的屬性,且嚴格按照MRO規定的順序向後查詢

class Person:
    school = 'MIT'

    def __init__(self, name, age, grade):
        self.name = name
        self.age = age
        self.grade = grade

    def lab(self):
        print('實驗')

class Society:
    def __init__(self, job):
        self.job = job

    def vocation(self, where):
        print(f'在{where}度假')

class Student(Society, Person):
    def __init__(self, course, job):
        super().__init__(job)  # super只能繼承第一個基類,如果繼承第二個基類的屬性則會報錯
        self.course = course

    def holiday(self, where):
        super().vocation(where)  # 繼承基類的函式屬性

stu1 = Student(course='控制工程', job='學生')
print(stu1.course)  # 控制工程

# 使用super載入基類中的屬性
print(stu1.job)  # 學生
stu1.holiday(where='黃金海岸')  # 在黃金海岸度假

方法三:方法一與方法二的組合

class Person:
    school = 'MIT'

    def __init__(self, name, age, grade):
        self.name = name
        self.age = age
        self.grade = grade

    def lab(self):
        print('實驗')


class Society:
    def __init__(self, job):
        self.job = job

    def vocation(self, where):
        print(f'在{where}度假')


class Student(Society, Person):
    def __init__(self, course, job, name, age, grade):
        Person.__init__(self, name, age, grade)
        super().__init__(job)  # super只能繼承第一個基類,如果繼承第二個基類的屬性則會報錯
        self.course = course

    def holiday(self, where):
        super().vocation(where)  # 繼承基類的函式屬性


stu1 = Student(course='Music', job='學生', name='lavigne', age=20, grade=2)
print(stu1.course)  # 控制工程

# 檢視基類中的屬性
print(stu1.job)  # 學生
print(stu1.name)  # lavigne
print(stu1.age)  # 20
stu1.holiday(where='黃金海岸')  # 在黃金海岸度假

總結:

方式一注意呼叫函式的時候要傳入self,方式二super函式繼承的是第一個基類

8. 組合

在一個類中,以另外一個類的物件作為資料屬性,稱為類的組合

class Course:
    def __init__(self, kind, teacher):
        self.kind = kind
        self.teacher = teacher

    def show_info(self):
        print(f'課程是{self.kind},教師是{self.teacher}')


class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    def show_birthday(self):
        return f'生日是{self.year}年{self.month}月{self.day}'


class Person:
    school = 'MIT'

    def __init__(self, name, age, grade):
        self.name = name
        self.age = age
        self.grade = grade


class Student:
    def __init__(self, name, age, grade, year, month, day):
        Person.__init__(self, name, age, grade)
        self.birthday = Date(year, month, day)  # 生日資訊,構建了一個物件
        self.courses = []  # 課程資訊

    def stu_info(self):
        print(f'學生是{self.name},年齡是{self.age},年級是{self.grade}'
              f'生日是{self.birthday.show_birthday()}')


stu1 = Student(name='ronaldo', age=20, grade=1, year=2000, month='Jan', day=1)
music = Course(kind='music', teacher='lavigne')
art = Course(kind='art', teacher='avril')
stu1.courses.append(music)  # 引數是一個物件
stu1.courses.append(art)  # 引數是一個物件
stu1.stu_info()  # 學生是ronaldo,年齡是20,年級是1生日是生日是2000年Jan月1
print(stu1.birthday.show_birthday())  # 生日是2000年Jan月1
stu1.courses[0].show_info()  # 課程是music,教師是lavigne

繼承:子類繼承了父類的資料和方法,衍生出自己的資料和方法

組合:將多個類組合到一起,組合後的類擁有多個類的資料和方法

9. 抽象類

9.1 概念

抽象類是一個特殊的類,只能被繼承,不能被例項化

如果說類是從一堆物件中抽取相同的內容而來的,那麼抽象類就是從一堆類中抽取相同的內容而來的,內容包括資料屬性和函式屬性。
比如我們有香蕉的類,有蘋果的類,有桃子的類,從這些類抽取相同的內容就是水果這個抽象的類,你吃水果時,要麼是吃一個具體的香蕉,要麼是吃一個具體的桃子,你永遠無法吃到一個叫做水果的東西。
從設計角度去看,如果類是從現實物件抽象而來的,那麼抽象類就是基於類抽象而來的。
從實現角度來看,抽象類與普通類的不同之處在於,抽象類中只能有抽象方法(沒有實現功能),該類不能被例項化,只能被繼承,且子類必須實現抽象方法

9.2 不重寫抽象類方法

from abc import ABC, abstractmethod
class Animal(ABC):
    def __init__(self, name):
        self.name = name
    @abstractmethod
    def run(self):
        pass

    @abstractmethod
    def speak(self):
        pass

    def eat(self):
        pass

class Dog(Animal):
    def __init__(self, name):
        super().__init__(name)

dog1 = Dog('小黑')  # 報錯,子類沒有定義抽象方法
# TypeError: Can't instantiate abstract class Dog with abstract methods run, speak

9.3 重寫抽象類方法

from abc import ABC, abstractmethod

class Animal(ABC):
    def __init__(self, name):
        self.name = name

    @abstractmethod  # 定義抽象方法,無需實現功能
    def run(self):
        pass

    @abstractmethod  # 定義抽象方法,無需實現功能
    def eat(self):
        pass

    def speak(self):
        pass

class Dog(Animal):
    def __init__(self, name):
        super().__init__(name)
    def run(self):  # 子類繼承抽象類,必須定義抽象方法
        print('跑起來了')

    def eat(self):
        print('進食')


dog1 = Dog('小黑')
dog1.run()  # 跑起來了
dog1.eat()  # 進食

相關文章