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 的順序