python面向對象的繼承-組合-02

  • 2019 年 10 月 7 日
  • 筆記

面向對象(OOP)的三大特徵:# 封裝、繼承、多態

繼承

什麼是繼承

繼承:# 是一種關係,描述兩個對象之間什麼是什麼的什麼的關係

例如:麥兜、佩奇、豬豬俠、豬剛鬣,都是豬

為什麼要使用繼承

繼承的好處:# 繼承的一方可以直接使用被繼承一方已經有的東西

程序中,繼承描述的是類和類之間的關係

​ 例如:a繼承了b,a就能直接使用b已經存在的方法和屬性

​ 此時,a稱之為子類,b稱之為父類,也稱之為基類。

為什麼使用繼承:# 其目的是為了重用已經有了的代碼,提高重用性

如何使用繼承

語法

class 類名稱(父類的名稱):    # 在python中 一個子類可以同時繼承多個父類

繼承小案例(子類直接用父類的方法,無需自己實現)

class Base:      desc = "這是一個基類"        def show_info(self):          print(self.des)        @staticmethod      def make_money():          print("一天賺ta一個億")      class SubClass:      @staticmethod      def make_money():          print("一天賺ta一百")      pass      class SubClass2(Base):      # 通過繼承使用父類的 make_money      pass      # 無繼承  obj1 = SubClass()  obj1.make_money()  # 一天賺ta一百    # 繼承,可得到父類的方法及屬性  obj2 = SubClass2()  obj2.make_money()  # 一天賺ta一個億  print(obj2.desc)  # 這是一個基類

管理學生與老師小案例(老師類默認有教書的方法,而學生類是不可以有的,所以不能直接讓學生類繼承老師類)

# 需求:管理老師  class Teacher:      def __init__(self, name, age, gender):          self.name = name          self.age = age          self.gender = gender        def say_hi(self):          print(f"name:{self.name},gender:{self.gender},age:{self.age}")      t1 = Teacher('jack', 'male', 20)  t1.say_hi()  # name:jack,gender:20,age:male      # 擴展需求:把老師也一起管理  class Student:      def __init__(self, name, age, gender, number):          self.name = name          self.age = age          self.gender = gender          self.number = number        def say_hi(self):          print(f"name:{self.name},gender:{self.gender},age:{self.age}")      s1 = Student('sushan', 'female', 18, 'xxx01')  s1.say_hi()  # name:sushan,gender:18,age:female

上面代碼有些重複,學生和老師有很多屬性都是一樣的。

抽象

直意:不具體、不清晰、很模糊、看不太懂

編程中:# 將多個子類的中相同的部分,進行抽取,形成一個新的類,這個過程也稱之為抽象

# 抽取老師學生的共同特徵,然後再繼承  class Person:      def __init__(self, name, age, gender):          self.name = name          self.age = age          self.gender = gender        def say_hi(self):          print(f"name:{self.name},gender:{self.gender},age:{self.age}")        pass      class Teacher(Person):        def teaching(self):          print("老師教學生,寫代碼....")      class Student(Person):        pass      t1 = Teacher('jack', 'male', 20)  t1.say_hi()  # name:jack,gender:20,age:male  t1.teaching()  # 老師教學生,寫代碼....    s1 = Student('rose', 'female', 20)  s1.say_hi()  # name:rose,gender:20,age:female  # s1.teaching()  # 報錯,找不到teaching(他沒有,他的父類也沒有)

如何正確使用繼承:

  • 1.先抽象(提取特徵)再繼承
  • 2.繼承一個已經現存的類,擴展或是修改原始的功能
class A:      text = 'haha'      class B(A):      text = 'heihei'  # 注釋掉訪問父級的      pass      b = B()  print(b.text)  # b自身沒有,找類,就不用訪問類的父類的了  # heihei    b.text = 'xixix'  print(b.text)  # b(對象)自身有,就不能找類了  # xixix

屬性的查找順序

查找順序:對象自身 –> 類 –> 父類 –> …父類的上級父類… –> Object –> 報錯

派生與覆蓋(重寫)

  • 派生# 當一個子類中出現了與父類中不同的內容時,這個子類就稱之為派生類
class Person:      @staticmethod      def say_hi():          print("hello")      # 這個Student子類不是派生,父類Person一模一樣。(這樣沒啥意思)  class Student(Person):      pass

通常子類都會寫一些新的代碼,不可能和父類完全一樣,即通常子類都是派生類

派生類就是子類的意思

  • 覆蓋# 也稱之為重寫(overrides)當子類出現了與父類名稱完全一樣的屬性或是方法,就是覆蓋
class Person:      @staticmethod      def say_hi():          print("hello")      # 這個Student子類不是派生,父類Person一模一樣。(這樣沒啥意思)  class Student(Person):      @staticmethod      def say_hi():  # 與父類的say_hi 重複,重寫、覆蓋          print("hello world!")      pass      s = Student()  s.say_hi()  # hello world!

練習:實現一個可以限制元素類型的容器(子類訪問父類中的內容)

補充知識點

​ 子類訪問父類的方法:# super(當前類名稱, self).你要調用的父類的屬性或方法

# 小練習:做一個可以限制元素類型的容器類型  class MyList(list):  # 繼承list,可以直接用list的一些方法屬性      def __init__(self, data_type):          super(MyList, self).__init__()  # 應規範,子類重寫父類方法的時候__init__初始化函數中要調用父類的__init__初始化函數          self.data_type = data_type        def append(self, obj):          '''          重寫父類的append方法          :param obj: 是要存儲的元素          :return: None          '''          if isinstance(obj, self.data_type):          # if type(obj) == self.data_type:  # 寫法二              super(MyList, self).append(obj)  # 這裡需要訪問父類的append 方法來完成真正的存儲操作          else:              print(f"非指定類型{self.data_type}!")      # 創建時指定要存儲的元素類型  str_list = MyList(str)  str_list.append('abc')  print(str_list[0])  # abc  str_list.append(1)  # 非指定類型<class 'str'>!

訪問父類屬性的三種方式

# 1.super(類, 對象自身).類的屬性/方法      python2的寫法(兼容寫法,python2、3都可以用)  # 2.super().類的屬性/方法      python3的新語法  ***** (推薦,python2項目慎用哦)  # 3.類.屬性/方法      沒啥實際意義,不是繼承,這是直接用類來調用了

代碼案例

# 子類訪問父類中的屬性  class Parent:      text = 'abc'        @staticmethod      def say_something():          print("anything")      class Sub(Parent):      def show_info(self):          # # 方式一: python2 和 3 都兼容          print(super(Sub, self).text)          super(Sub, self).say_something()          #          # # 方式二:python 3 中的語法   *** 推薦          print(super().text)          super().say_something()          #          # # 方式三:沒啥意義,不是繼承,指名道姓的調用          print(Parent.text)          Parent.say_something()          pass      s = Sub()  s.show_info()  # ----- 方式一  # abc  # anything  # ----- 方式二  # abc  # anything  # ----- 方式三  # abc  # anything

強調點

​ 如果子類繼承了一個現有的類,並且覆蓋了父類的__init__方法時,那麼必須在__init__方法中的第一行必須調用父類中的__init__方法,並傳入父類所需的參數。 — 這是重點 —

上面案例改版(沒有調用父類的__init__方法,父類可能沒有初始化完成,後續可能會導致一些意想不到的問題)

class Person:      def __init__(self, name, gender, age):          self.name = name          self.gender = gender          self.age = age          self.say_hello()  # 初始化時要調用的函數        def say_hi(self):          print(f"name:{self.name},gender:{self.gender},age:{self.age}")        def say_hello(self):          print(f"Hello, i'm {self.name}")      class Student:      def __init__(self, name, gender, age, number):          self.name = name          self.gender = gender          self.age = age          self.number = number        def say_hi(self):          print(f"name:{self.name},gender:{self.gender},age:{self.age}")          print(f"number:{self.number}")      # 上述代碼優點冗餘,怎麼簡化?  class Student2(Person):      def __init__(self, name, gender, age, number):          super().__init__(name, gender, age)  # 不調用父類的__init__方法就會使父類的初始化函數中的say_hello方法,初始化就不能算是完成 ***          self.number = number        def say_hi(self):          super().say_hi()          print(f"number:{self.number}")      stu = Student2("rose", 'female', 18, 'young1')  # Hello, i'm rose  stu.say_hi()  # name:rose,gender:female,age:18  # number:young1

組合

組合:# 也是一種關係,描述的是兩個對象之間是什麼有什麼的關係,將一個對象作為另一個對象的屬性(即什麼有什麼)

例如:學生有手機、遊戲中的角色擁有某些裝備

組合無處不在,數據類型、函數都是對象,都有組合

組合的目的:# 重用現有代碼

# 讓學生使用手機打電話、發短訊  class Phone:      def __init__(self, price, kind, color):          self.price = price          self.kind = kind          self.color = color        @staticmethod      def call():          print("正在呼叫xxx...")        @staticmethod      def send_msg():          print("正在發送....")      class Student:      def __init__(self, name, gender):          self.name = name          self.gender = gender        def show_info(self):          print(f"name:{self.name}, gender:{self.gender}")      # 讓學生擁有打電話這個功能(有聯繫)  stu1 = Student('rose', 'female')  phone1 = Phone(1888, 'vivo', 'red')  phone1.call()  # 正在呼叫xxx...    # 組合:把一個對象作為另一個對象的屬性  class Student2:      def __init__(self, name, gender, phone):          self.name = name          self.gender = gender          self.phone = phone        def show_info(self):          print(f"name:{self.name}, gender:{self.gender}")    phone2 = Phone(1888, 'vivo', 'red')  stu2 = Student2('rose', 'female', phone2)    stu2.phone.call()  # 正在呼叫xxx...  stu2.phone.send_msg()  # 正在發送....

組合與繼承的取捨

'''      繼承:分析兩個類的關係,到底是不是:什麼是什麼的關係      組合:如果兩個類之間,沒有太大的關係,完全不屬於同類        另外:組合相比繼承,耦合度更低  '''

菱形繼承(了解)

多繼承帶來的問題:python支持多繼承,雖然靈活,但會帶來名稱衝突的問題(到底找誰的)

新式類與經典類

python3 中任何類都是直接或間接繼承自object

新式類:任何顯式或隱式地繼承自object的類就稱之為新式類(即python3 中的類全是新式類)

經典類:不是object的子類,僅在python2 中出現

擴展

# 在python2 中可能有這樣子的代碼  class Person(object):  # 默認讓python2 中的類也是新式類,兼容寫法      pass

mro列表(只在python3 中有)

調用方式:# 類.mro() --> 可以獲取到類的 **mro 列表**,裏面的元素就是類的查找順序

class Parent:      pass      class Sub(Parent):      pass    print(Sub.mro())  # [<class '__main__.Sub'>, <class '__main__.Parent'>, <class 'object'>]  # 從左到右就是這個類的查找順序,先Sub自身 再Parent 再object

​ 當使用super()函數時,python3會在mro列表上繼續搜索下一個類。如果每個重定義的方法統一使用super()並只調用它一次,那麼控制流最終會遍歷完整個mro列表,每個方法也只會被調用一次

注意注意注意:使用super調用的所有屬性,都是從mro列表當前的位置往後找,千萬不要通過看代碼去找繼承關係,一定要看mro列表

類的屬性的查找順序

新式類中的菱形繼承

新式類中的查找順序

類的屬性查找順序:

新式類:先找自身,再先深度找,如果有共同父類再廣度找(直接看類的mro列表就知道查找順序了 類.mro() )

經典類: python2中的經典類就是深度優先

# 此段代碼指定時python2 運行  # 注釋掉不同類中的num 來測試查找順序  class B:      # num = 2      pass      class C:      # num = 3      pass      class E(B):      # num = 5      pass      class F(C):      # num = 6      pass      class G(C):      num = 7      pass      class H(E, F, G):      # num = 8      pass    print(H.num)  # print(H.mro())  # python2 中沒有 mro()  # [<class '__main__.H'>, <class '__main__.E'>, <class '__main__.B'>, <class '__main__.F'>, <class '__main__.G'>, <class '__main__.C'>, <class 'object'>]  # [H, E, B, F, G, C, object]  ---> 上面的mro簡化表示順序(這是python3 的順序)  # [H, E, B, F, C, G, object]  ---> 這是python2 的順序