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() # 進食