1. 程式人生 > 其它 >第十一章 (補充)面向物件繼承

第十一章 (補充)面向物件繼承

目錄

繼承

繼承簡介

  1. 繼承是一種建立新類的方式,新建的類可稱為子類或派生類,父類可稱為基類或超類
  2. python支援多繼承,新建的類可以支援一個或多個父類
'''單繼承和多繼承簡單定義'''
class Parent1:
    pass
class Parent2:
    pass
class Sub1(Parent1): #單繼承
    pass
print(Sub1.__bases__)  # 檢視自己的父類---->(<class '__main__.Parent1'>,)
 
class Sub2(Parent1,Parent2): # 多繼承
    pass
print(Sub2.__bases__)    # 檢視自己的父類---->(<class '__main__.Parent1'>, <class '__main__.Parent2'>)

經典類與新式類

在py2中有經典類和新式類的區別:

  • 新式類:繼承了object類的子類,以及該子類的子類,子子類
  • 經典類:沒有繼承object類的子類,以及該子類的子類,子子類
'''py2中'''
class Foo:
    pass     # 經典類
class Bar(object):
    pass     # 新式類

注意:在py3中沒有繼承任何類,預設繼承object類,所以python3中都是新式類

'''py3中'''
class Foo():
    pass
print(Foo.__bases__) # --->(<class 'object'>,),預設繼承object類
 
class Sub(Foo):
    pass
 
print(Sub.__bases__) # ---->(<class '__main__.Foo'>,)

類繼承解決了什麼問題

  • 類解決物件與物件之間程式碼冗餘的問題,子類可以遺傳父類的屬性
  • 繼承解決的是類與類之間程式碼冗餘的問題
  • object類豐富了程式碼的功能

示例如下:

'''學生選課系統和老師打分功能'''
# 學生類
class Student():
    def __init__(self,name,age,gender,course = None):
        self.name = name
        self.age = age
        self.gender = gender
        self.course = course
    # 定義一個選課的方法
    def choose_course(self,course):
        if self.course is None:
            self.course = []
        self.course.append(course)
        print(f"Student choice class --->{self.course}")
 
# 教師類
class Teacher():
    def __init__(self,name,age,gender,level):
        self.name = name
        self.age = age
        self.gender = gender
        self.level = level
    # 定義一個打分方法
    def make_score(self,stu_obj,score):
        stu_obj.score = score
        print(f'Teacher{self.name} make {stu_obj.score} to {stu_obj.name}! ')
    
'''有很多冗餘的程式碼,優化一下,定義一個人的類整合一下重複的程式碼'''
# 人類
class Human():
    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender
 
 
# 學生類
class Student(Human):
    def __init__(self, name, age, gender, score=None, course=None):
        Human.__init__(self, name, age, gender)
        self.score = score
        self.course = course
 
    # 定義一個選課的方法
    def choose_course(self, course):
        if self.course is None:
            self.course = []
        self.course.append(course)
        print(f"Student choice class --->{self.course}")
 
 
# 教師類
class Teacher(Human):
    def __init__(self, name, age, gender, level):
        Human.__init__(self, name, age, gender)
        self.level = level
 
    # 定義一個打分方法
    def make_score(self, stu_obj, score):
        stu_obj.score = score
        print(f'Teacher{self.name} make {stu_obj.score}marks to {stu_obj.name}! ')
 
 
# 學生類例項化
stu = Student('HammerZe', 18, 'male')
stu.choose_course('python')
 
# 教師類例項化
teacher = Teacher('li', 18, 'male', 10)
teacher.make_score(stu, 90)
 
Student choice class --->['python']
Teacherli make 90marks to HammerZe! 
 
 

多繼承的優缺點

  • 優點:子類可以同時遺傳多個父類的屬性,最大限度的重用程式碼
  • 缺點:違反人的思維習慣,一個人有兩個爹,程式碼的可讀性會變差,不建議使用多繼承,如果不可避免多個父類的繼承,應該使用Mixins機制
  • 繼承表達的是一種“是”什麼關係

Mixins機制

  • 多繼承的正確開啟方式:mixins機制
  • mixins機制核心:就是在多繼承背景下儘可能底提升多繼承的可讀性
  • 讓多繼承滿足人的思維習慣--->什麼“是”什麼
class Vehicle:
    pass
class FlyableMixin:   # 規範多繼承
    def fly(self):
        pass
# 民航飛機
class CiviAircraft(FlyableMixin,Vehicle)
	pass
#直升機
class Helicopter(FlyableMixin,Vehicle)
	pass
class Car(Vehicle)
	pass 
 
'''表達是的關係,放在繼承的末尾,特們都是交通工具'''
 
'''eg:飛機有飛的功能,是交通工具'''
 
如果有多個功能就必須寫多個Mixin類!

繼承的查詢順序

  • 物件>子類>父類>父父類
  • 單繼承背景下屬性查詢

示例如下:

class Foo():
    def f1(self):
        print('Foo.f1')
 
    def f2(self):
        print('Foo.f2')
        self.f1()
 
class Bar(Foo):
    def f1(self):
        print('Bar.f1')
 
 
obj = Bar()
obj.f2()
# 結果
Foo.f2
Bar.f1
 
'''查詢順序:
1.obj先從obj名稱空間找,再從Bar名稱空間中找,沒有f2去他爹(Foo)中找
2.執行Foo中得f2,遇到self.f1()此時self是obj,是Bar的物件
3.執行Bar中的f1
'''
 
 
# 區別下:父類不想讓子類的方法覆蓋,可以私有化
class Foo:
    def __f1(self):  # _Foo__f1()
        print('Foo.f1')
 
    def f2(self):
        #
        print('Foo.f2')
        self.__f1()  # _Foo__f1()
 
 
class Bar(Foo):
    def __f1(self):  # # _Bar__f1()
        print('Bar.f1')
 
obj = Bar()
obj.f2()
 
# 結果
Foo.f2
Foo.f1
'''Foo中f1私有化,所以輸出的是Foo中的f1'''

多繼實現原理

菱形結構

在python中可以繼承多個類,這樣就會引發下面的結構:

  • 當D繼承B和C,B、C分別繼承A就會組成一個菱形的繼承關係,這樣就會涉及到查詢屬性的順序問題,A、B、C、中如果方法重名,輸出的順序是按mro列表輸出的順序繼承

示例如下:

'''py3中'''
 
class A():
    def out_text(self):
        print('from A')
 
class B(A):
    def out_text(self):
        print('from B')
 
class C(A):
    def out_text(self):
        print('from C')
 
class D(B,C):
    pass
 
obj = D()
obj.out_text() # 結果---->from B
''' 可以打印出mro列表檢視順序'''
print(D.mro())
# [<class '__main__.D'>,
# <class '__main__.B'>, 
# <class '__main__.C'>, 
# <class '__main__.A'>,
# <class 'object'>]
 
'''這樣看來查詢順序就顯而易見了,
1、從D中找out_text方法,沒有直接去B
2、B中有out_text方法,直接輸出停止查詢'''
  • mro列表查詢準則:
    1. 子類先查,再查父類
    2. 當繼承多個父類的時候,按mro列表順序被檢查
    3. 如果繼承多個類,被繼承類內具有相同的方法,先輸出mro列表左邊類的方法
  • 注意:mro列表可以寫成__mro__也可以,呼叫mro方法的必須是起始類,obj是D的物件,所以用D.mro()
  • mro列表是通過一個C3線性演算法來實現的

非菱形結構


程式碼實現如下:

'''py3中'''
 
 
class E:
    pass
 
 
class F:
    pass
 
 
class B(E):
    pass
 
 
class C(F):
    pass
 
 
class D:
    def test(self):
        print('from D')
 
 
class A(B, C, D):
    pass
 
 
print(A.mro())
'''
查詢順序如下:
[<class '__main__.A'>, 
<class '__main__.B'>, 
<class '__main__.E'>, 
<class '__main__.C'>, 
<class '__main__.F'>, 
<class '__main__.D'>,
 <class 'object'>]
'''
 
obj = A()
obj.test()  
# 結果為:from D

深度優先和廣度優先

深度優先:

  • 經典類:按深度優先查詢

經典類查詢順序如下:


在py2中,沒有繼承object的類及其子類都是經典類

程式碼實現:

'''py2中'''
class G:
    def test(self):
        print('from G')
class E(G):
    def test(self):
        print('from E')
class F(G):
    def test(self):
        print('from F')
class B(E):
    def test(self):
        print('from B')
class C(F):
    def test(self):
        print('from C')
class D(G):
    def test(self):
        print('from D')
 
class A(B, C, D):
    pass
 
 
obj = A()
obj.test()  # 查詢順序為:obj->A->B->E->G->C->F->D->object
 
# 結果
from B

廣度優先:

  • 新式類:按廣度優先順序查詢

新式類查詢順序如下:

在py3中,預設為新式類

程式碼實現如下:

'''py3中'''
class G:
    def test(self):
        print('from G')
class E(G):
    pass
class F(G):
    pass
class B(E):
    pass
class C(F):
    pass
class D(G):
    def test(self):
        print('from D')
 
class A(B, C, D):
    pass
 
 
obj = A()
obj.test() # 查詢順序為:obj->A->B->E->C->F->D->G->object
 
# 結果
from D

super()方法

super()方法的存在就是為了解決多重繼承的問題,在一個父類中使用super()方法用於呼叫下一個父類的方法

  • super方法
class A:
    def test(self):
        print('from A')
        super().test()
'''用於呼叫下一個父類的方法B.test'''
 
class B:
    def test(self):
        print('from B')
 
 
class C(A, B):
    pass
 
 
c = C()
c.test()
print(C.mro())
# 查詢順序如下
#[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
 
# 結果
from A
from B

抽象類

python的抽象類需要藉助模組實現,抽象類是一個特殊的類,它只能被繼承,不能被例項化

  • 作用:在不同的模組中通過抽象基類來呼叫,可以用最精簡的方式展示出程式碼之間的邏輯關係,讓模組之間的依賴清晰簡單,使得程式碼的可讀性變高。
  • 注意:子類繼承抽象類的時候,必須定義相同方法對抽象類的方法進行覆蓋
import abc
# 抽象類: 抽象類只能被繼承,不能被例項化
class Animal(metaclass=abc.ABCMeta):
 
    @abc.abstractmethod  # 該方法已經是抽象方法了
    def speak(self): pass
 
    @abc.abstractmethod
    def login(self):pass
 
class People(Animal):
    def speak(self):
        print('嗷嗷嗷')
 
    def login(self):
        pass
 
 
class Pig(Animal):
    def speak(self):
        print('哼哼哼')
 
 
class Dog(Animal):
    def speak(self):
        print('汪汪汪')
 
 
obj = People()
obj.speak()

方法補充:

  1. sel.__class__檢視物件所屬類

  2. 類名/物件名.__dict__檢視類/物件名稱空間

  3. 類名/物件名.__bases__檢視父類

  4. 起始類名.__mro__列印繼承順序,py3從左到右查詢

  5. locals()檢視區域性名稱空間

  6. globals()檢視全域性名稱空間

  7. dirs(str)檢視字串所搭配的內建方法有哪些,檢視內容可換