類的繼承和派生

zenopan發表於2024-05-06

【一】繼承介紹

# 面向對線三大特性:繼承 封裝 多型
# 繼承

# 【一】什麼是繼承
# 繼承就是一種建立新類的方式,新建的類可以繼承一個或多個類的屬性。
# 新的類如果有自己的屬性,那就叫派生

# 【二】繼承的優點
# 可以繼承父類的所有屬性和方法,實現程式碼的去重

# 【三】繼承方式
# 單繼承:繼承一個父類的子類
# 多繼承:繼承多個父類的子類

# class Student(School):
# 繼承的類叫父類 School
# 新建的類叫子類 Student


class Person(object):
    height = 180
    weight = 50


class School(object):
    school = "清華"


# 【1】單繼承:只繼承一個父類
class Student(Person):
    def __init__(self, name):
        self.name = name

    def tell_me(self):
        print(f"我是 {self.name} 我身高 {self.height} 體重 {self.weight}")


stu1 = Student("dream")
stu1.tell_me()


# 【2】多繼承:繼承兩個以上的父類
class Teacher(School, Person):
    def __init__(self, name):
        self.name = name

    def tell_me(self):
        print(f"我是 {self.name} 我身高 {self.height} 體重 {self.weight} 學校在 {self.school}")


teacher = Teacher("opp")
teacher.tell_me()

# 【3】如何檢視繼承的父類
# (1)檢視當前繼承的父類 __base__ : 如果繼承多個父類,預設只列印第一個繼承的父類
print(Student.__base__)  # <class '__main__.Person'>
print(Teacher.__base__)  # <class '__main__.Person'>
# (2)檢視當前繼承的父類們 : __bases__ 一個元組,元組中放的是所有繼承的父類
print(Student.__bases__)  # (<class '__main__.Person'>,)
print(Teacher.__bases__)  # (<class '__main__.Person'>, <class '__main__.School'>)

【二】經典類和新式類

# 經典類和新式類的區別在於Python版本的不同
# 在 py3 版本之前存在兩個概念 ,在之後就沒有經典類的概念了,只有新式類
# 【一】什麼是經典類
# 在py2中沒有顯示繼承 object 的類或者是該類的子類都是經典類
# 【二】什麼是新式類
# 在py2中顯示繼承 object 的類或者是該類的子類都是新式類
# 在py3之後所有的類預設都是新式類,不寫m預設繼承 object

【三】繼承和抽象

# 什麼是繼承和抽象
# 繼承是子類和父類之間的關係,什麼是什麼的概念
# 抽象相當於將某部分抽立起來再總結

# 【一】抽象
# 將不同的類。根據指定的表徵總結起來歸介於一個類
# 貓和狗 --> 動物
# 豬八戒 麥兜 ---> 豬
# 梅西 奧巴馬 --> 人
# 豬 和 人 ---> 動物

# 【二】繼承
# 基於抽象的結果,然後用語言去實現


# 【三】繼承和抽象
# 繼承是由少變多
# 抽象是由多變少

# 【四】在python中實現繼承
# 【1】沒有繼承和抽象
# 有一隻貓
class Cat(object):
    def speak(self):
        print(f"貓可以喵喵叫")

    def eat(self):
        print("貓可以吃飯")

    def drink(self):
        print("貓可以喝水")


# 有一隻狗
class Dog(object):
    def speak(self):
        print(f"狗可以旺旺叫")

    def eat(self):
        print("狗可以吃飯")

    def drink(self):
        print("狗可以喝水")


# 【2】抽象
# 總結起來一個公共的類,可以吃喝拉撒睡
class Animal(object):
    def speak(self):
        print(f"{self.name}可以叫")

    def eat(self):
        print(f"{self.name}可以吃飯")

    def drink(self):
        print(f"{self.name}可以喝水")

# 【3】繼承
# 有一隻貓
class Cat(Animal):

    def __init__(self, name):
        self.name = '貓'+name


# 有一隻狗
class Dog(Animal):
    def __init__(self, name):
        self.name = '狗'+name

cat_one = Cat(name='小花')
print(cat_one.speak())
dog_one = Dog(name="旺財")
print(dog_one.speak())

【四】封裝後繼承的屬性查詢順序

【1】封裝之前的屬性查詢順序

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

    # 【四】在父類 Foo 裡面找到了 f2
    def f2(self):
        # 【五】列印 Foo.f2
        print('Foo.f2')
        # 【六】self.f1 ---> self 是誰 ?
        # 觸發的是 Foo.f1 還是 Bar.f1?
        # Foo.f1 3
        # Bar.f1 2
        self.f1()


class Bar(Foo):
    # 【七】因為是 透過Bar例項化得到的物件,所以 self 就是 Bar
    def f1(self):
        # 【八】列印 Bar.f1
        print('Bar.f1')
    # 【三】Bar裡面沒有f2,去父類找 Foo


# 【一】類例項化得到物件
b = Bar()
# 【二】物件呼叫方法 f2
b.f2()
class B(object):
    def read(self):
        print(self)

class A(B):
    ...


a = A()
a.read()

b = B()
b.read()

# 哪個物件呼叫方法,self 就是誰

【2】有封裝的時候的繼承

class Foo:
    # __f1 --> _Foo__f1
    def __f1(self):
        print('Foo.f1')

    # 【4】在Foo裡面找到了 f2
    def f2(self):
        # 【5】列印 Foo.f2
        print('Foo.f2')
        # 【6】
        # 沒有變形的時候 self 是誰就去誰裡面找
        # 但是變形之後 就只能在自己的類裡面找,沒辦跳到別的類裡面
        # Foo.f1 2
        # Bar.f1 3
        self.__f1()


class Bar(Foo):
    # 【3】沒有f2. 去 Foo
    def __f1(self):
        print('Bar.f1')


# 【1】
b = Bar()
# 【2】
b.f2()

【3】總結

# 如果屬性不封裝的情況下,誰例項化得到的self 就去誰裡面找
# 如果屬性封裝的情況下 , 誰例項化得到的self 無效,只能在當前所在的類的民稱空間裡面找

【五】派生

# 派生是指,子類繼承父類,派生出自己的屬性與方法,並且重用父類的屬性與方法

【1】子類繼承父類的屬性

class People:
    school = '清華大學'

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


class Teacher(People):
    # 派生 : 派生出自己新的屬性,在進行屬性查詢時,子類中的屬性名會優先於父類被查詢
    def __init__(self, name, sex, age, title):
        self.name = name
        self.sex = sex
        self.age = age
        self.title = title

    def teach(self):
        print('%s is teaching' % self.name)


# 只會找自己類中的__init__,並不會自動呼叫父類的
obj = Teacher('dream', 'male', 18, '高階講師')

print(obj.name, obj.sex, obj.age, obj.title)
# dream male 18 高階講師

【2】繼承方式一

  • 指名道姓的呼叫某一個類的函式
class People:
    school = '清華大學'

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


class Teacher(People):
    # 派生 : 派生出自己新的屬性,在進行屬性查詢時,子類中的屬性名會優先於父類被查詢
    def __init__(self, name, sex, age, title):
        # 直接呼叫 父類 中 的 __init__ 方法
        # 呼叫的是函式,因而需要傳入self
        People.__init__(self, name, age, sex)
        self.title = title

    def teach(self):
        print('%s is teaching' % self.name)


# 只會找自己類中的__init__,並不會自動呼叫父類的
obj = Teacher('dream', 'male', 18, '高階講師')

print(obj.name, obj.sex, obj.age, obj.title)
# dream male 18 高階講師

【3】繼承方式二

  • 呼叫super()會得到一個特殊的物件
  • 該物件專門用來引用父類的屬性
  • 且嚴格按照MRO規定的順序向後查詢
class People:
    school = '清華大學'

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


class Teacher(People):
    # 派生 : 派生出自己新的屬性,在進行屬性查詢時,子類中的屬性名會優先於父類被查詢
    def __init__(self, name, sex, age, title):
        # 直接呼叫 父類 中 的 __init__ 方法
        # 呼叫的是繫結方法,因此會自動傳入self,但是需要傳入相應的引數
        super().__init__(name, sex, age)
        self.title = title

    def teach(self):
        print('%s is teaching' % self.name)


# 只會找自己類中的__init__,並不會自動呼叫父類的
obj = Teacher('dream', 'male', 18, '高階講師')

print(obj.name, obj.sex, obj.age, obj.title)
# dream male 18 高階講師

【六】組合

# 在一個類中以另外一個類的物件作為資料屬性,稱為類的組合。
class Course:
    def __init__(self, name, period, price):
        self.name = name
        self.period = period
        self.price = price

    def tell_info(self):
        print(f'當前課程名字 {self.name} 當前課程週期 {self.period} 當前課程價格 {self.price}')


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

    def tell_birth(self):
        print(f'當前生日 {self.year} 年 {self.mon} 月 {self.day} 日')


class People:
    school = '清華大學'

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


# Teacher類基於繼承來重用People的程式碼
# 基於組合來重用Date類和Course類的程式碼
class Teacher(People):
    # 老師是人
    def __init__(self, name, sex, age, title, year, mon, day):
        super().__init__(name, age, sex)
        # 老師有生日
        self.birth = Date(year, mon, day)
        # 老師有課程,可以在例項化後,往該列表中新增Course類的物件
        self.courses = []

    def teach(self):
        print(f'當前老師正在授課 {self.name}')


python = Course('python', '3mons', 3000.0)
linux = Course('linux', '5mons', 5000.0)
teacher1 = Teacher('dream', 'male', 18, '金牌講師', 1987, 3, 23)

# teacher1有兩門課程
teacher1.courses.append(python)
teacher1.courses.append(linux)

# 重用Date類的功能
teacher1.birth.tell_birth()

# 重用Course類的功能
for obj in teacher1.courses:
    obj.tell_info()

# 當前生日 1987 年 3 月 23 日
# 當前課程名字 python 當前課程週期 3mons 當前課程價格 3000.0
# 當前課程名字 linux 當前課程週期 5mons 當前課程價格 5000.0

【六】組合和繼承的區別

  • 組合與繼承都是有效地利用已有類的資源的重要方式。但是二者的概念和使用場景皆不同,

【1】繼承的方式

  • 透過繼承建立了派生類與基類之間的關係,它是一種'是'的關係,比如白馬是馬,人是動物。
  • 當類之間有很多相同的功能,提取這些共同的功能做成基類,用繼承比較好,比如老師是人,學生是人

【2】組合的方式

  • 用組合的方式建立了類與組合的類之間的關係,它是一種‘有’的關係,比如教授有生日,教授教python和linux課程,教授有學生s1、s2、s3...

相關文章