面向對象三大特性
封裝 根據 職責 將 屬性 和 方法 封裝 到一個抽象的 類 中
繼承實現代碼的重用,相同的代碼不需要重復的編寫
多態 不同的對象調用相同的方法,產生不同的執行結果,增加代碼的靈活度
1、單繼承
1.1、 繼承的概念、語法和特點
繼承的概念:子類 擁有 父類 的所有 方法 和 屬性

class Animal: def eat(self): print("吃") def drink(self): print("喝") def run(self): print("跑") def sleep(self): print("睡") class Dog: def eat(self): print("吃") def drink(self): print("喝") def run(self): print("跑") def sleep(self): print("睡") def bark(self): print("汪汪叫") # 創建一個對象 - 狗對象 wangcai = Dog() wangcai.eat() wangcai.drink() wangcai.run() wangcai.sleep() wangcai.bark()
1.1.1、 繼承的語法
class 類名(父類名): pass

class Animal: def eat(self): print("吃---") def drink(self): print("喝---") def run(self): print("跑---") def sleep(self): print("睡---") class Dog(Animal): # 子類擁有父類的所有屬性和方法 # def eat(self): # print("吃") # # def drink(self): # print("喝") # # def run(self): # print("跑") # # def sleep(self): # print("睡") def bark(self): print("汪汪叫") # 創建一個對象 - 狗對象 wangcai = Dog() wangcai.eat() wangcai.drink() wangcai.run() wangcai.sleep() wangcai.bark()
子類 繼承自 父類,可以直接 享受 父類中已經封裝好的方法,不需要再次開發
子類 中應該根據 職責,封裝 子類特有的屬性和方法
1.1.2、 專業術語
Dog
類是 Animal
類的子類,Animal
類是 Dog
類的父類,Dog
類從 Animal
類繼承
Dog
類是 Animal
類的派生類,Animal
類是 Dog
類的基類,Dog
類從 Animal
類派生
1.1.3、繼承的傳遞性
C
類從 B
類繼承,B
類又從 A
類繼承
那么 C
類就具有 B
類和 A
類的所有屬性和方法
子類 擁有 父類 以及 父類的父類 中封裝的所有 屬性 和 方法

class Animal: def eat(self): print("吃---") def drink(self): print("喝---") def run(self): print("跑---") def sleep(self): print("睡---") class Dog(Animal): def bark(self): print("汪汪叫") class XiaoTianQuan(Dog): def fly(self): print("我會飛") class Cat(Animal): def catch(self): print("抓老鼠") # 創建一個哮天犬的對象 xtq = XiaoTianQuan() xtq.fly() xtq.bark() xtq.eat() xtq.catch()
提問:哮天犬 能夠調用 Cat
類中定義的 catch
方法嗎?
答案:不能,因為 哮天犬 和 Cat
之間沒有 繼承 關系。
1.2 方法的重寫
子類 擁有 父類 的所有 方法 和 屬性
子類 繼承自 父類,可以直接 享受 父類中已經封裝好的方法,不需要再次開發
應用場景
當 父類 的方法實現不能滿足子類需求時,可以對方法進行 重寫(override)
重寫 父類方法有兩種情況:
①覆蓋 父類的方法
②對父類方法進行 擴展
1.2.1、覆蓋父類的方法
如果在開發中,父類的方法實現 和 子類的方法實現,完全不同
就可以使用 覆蓋 的方式,在子類中重新編寫 父類的方法實現
具體的實現方式,就相當於在 子類中 定義了一個 和父類同名的方法並且實現
重寫之后,在運行時,只會調用 子類中重寫的方法,而不再會調用 父類封裝的方法

class Animal: def eat(self): print("吃---") def drink(self): print("喝---") def run(self): print("跑---") def sleep(self): print("睡---") class Dog(Animal): def bark(self): print("汪汪叫") class XiaoTianQuan(Dog): def fly(self): print("我會飛") def bark(self): print("叫得跟神一樣...") xtq = XiaoTianQuan() # 如果子類中,重寫了父類的方法 # 在使用子類對象調用方法時,會調用子類中重寫的方法 xtq.bark()
1.2.2、對父類方法進行 擴展
如果在開發中,子類的方法實現 中 包含父類的方法實現
父類原本封裝的方法實現 是 子類方法的一部分,就可以使用 擴展 的方式
在子類中重寫 父類的方法,在需要的位置使用 super().父類方法
來調用父類方法的執行,代碼其他的位置針對子類的需求,編寫 子類特有的代碼實現
調用父類方法的另外一種方式(知道)
在python2.x中如果需要調用父類的方法,還可以使用以下方式:
父類名.方法(self)
這種方式,目前在 Python 3.x
還支持這種方式
這種方法 不推薦使用,因為一旦 父類發生變化,方法調用位置的 類名 同樣需要修改
提示
在開發時,父類名
和 super()
兩種方式不要混用
如果使用 當前子類名 調用方法,會形成遞歸調用,出現死循環
1.3 父類的 私有屬性 和 私有方法
子類對象 不能 在自己的方法內部,直接 訪問 父類的 私有屬性 或 私有方法
子類對象 可以通過 父類 的 公有方法間接 訪問到 私有屬性 或 私有方法
示例
B
的對象不能直接訪問__num2
屬性B
的對象不能在demo
方法內訪問__num2
屬性B
的對象可以在demo
方法內,調用父類的test
方法- 父類的
test
方法內部,能夠訪問__num2
屬性和__test
方法

class A: def __init__(self): self.num1 = 100 self.__num2 = 200 def __test(self): print("私有方法 %d %d" % (self.num1, self.__num2)) class B(A): def demo(self): # 1. 在子類的對象方法中,不能訪問父類的私有屬性 # print("訪問父類的私有屬性 %d" % self.__num2) # 2. 在子類的對象方法中,不能調用父類的私有方法 # self.__test() pass # 創建一個子類對象 b = B() print(b) b.demo() # 在外界不能直接訪問對象的私有屬性/調用私有方法 # print(b.__num2) # b.__test()

class A: def __init__(self): self.num1 = 100 self.__num2 = 200 def __test(self): print("私有方法 %d %d" % (self.num1, self.__num2)) def test(self): print("父類的公有方法 %d" % self.__num2) self.__test() class B(A): def demo(self): # 1. 在子類的對象方法中,不能訪問父類的私有屬性 # print("訪問父類的私有屬性 %d" % self.__num2) # 2. 在子類的對象方法中,不能調用父類的私有方法 # self.__test() # 3. 訪問父類的公有屬性 print("子類方法 %d" % self.num1) # 4. 調用父類的公有方法 self.test() pass # 創建一個子類對象 b = B() print(b) b.demo() # 在外界訪問父類的公有屬性/調用公有方法 # print(b.num1) # b.test() # 在外界不能直接訪問對象的私有屬性/調用私有方法 # print(b.__num2) # b.__test()
class Person: def __init__(self, name, age): self.name = name self.__age = age print('父類的構造方法被調用') @property def age(self): return self.__age @age.setter def age(self, age): self.__age = age # 成員方法 def show(self): print("父類的show") class Worker(Person): def __init__(self,name,age,num): # 在子類的構造函數中調用父類的構造方法 # 方式一: super().__init__(name, age) print('子類的構造函數被調用了') self.num = num # # 方式二: # Person.__init__(name,age) class Doctor(Person): def __init__(self, name, age, num): super().__init__(name, age) self.num = num def show(self): print("Doctor show") p1 = Person('Tom', 23) p1.show() # >> 父類的show print(p1.age) # 創建子類對象 w1 = Worker('Jack', 21, 2) # 子類對象可以調用父類對象中未被私有化的方法 w1.show() # >>父類的show #子類訪問父類私有屬性,只需要父類中提供@property裝飾的方法【get和set方法】 print(w1.age) print(p1.name) #>> tom d1 = Doctor("lili", 26, 2) d1.show() #>>Doctor show print(d1.name)
a.子類的獨享可以直接訪問父類中未被私有化的屬性,訪問私有化屬性時,需要在父類提供get和set方法
b.子類對象可以調用父類中未被私有化的方法
c.父類對象無法訪問子類對象的屬性和方法
優缺點:
優點:
可以簡化代碼,減少冗余
可以提高代碼的維護性
可以提高代碼的安全性
繼承是多態的前提
缺點:
耦合和內聚被用來描述類與類之間的關系,一般高內聚低耦合說明代碼比較好
繼承關系中耦合性相對較高【如果修改父類,則所有子類需求都會改變】
02.多繼承
概念
子類 可以擁有 多個父類,並且具有 所有父類 的 屬性 和 方法
例如:孩子 會繼承自己 父親 和 母親 的 特性
語法
class 子類名(父類名1, 父類名2...) pass
class A: def test(self): print("test 方法") class B: def demo(self): print("demo 方法") class C(A, B): """多繼承可以讓子類對象,同時具有多個父類的屬性和方法""" pass # 創建子類對象 c = C() c.test() c.demo()
2.1 多繼承的使用注意事項
問題的提出
如果 不同的父類 中存在 同名的方法,子類對象 在調用方法時,會調用 哪一個父類中的方法呢?
注意:開發時,應該盡量避免這種容易產生混淆的情況! —— 如果 父類之間 存在 同名的屬性或者方法,應該 盡量避免 使用多繼承
Python 中的 MRO —— 方法搜索順序(知道)
Python
中針對 類 提供了一個 內置屬性 __mro__
可以查看 方法 搜索順序
MRO 是 method resolution order
,主要用於 在多繼承時判斷 方法、屬性 的調用 路徑
print(C.__mro__)
在搜索方法時,是按照 __mro__
的輸出結果 從左至右 的順序查找的
如果在當前類中 找到方法,就直接執行,不再搜索
如果 沒有找到,就查找下一個類 中是否有對應的方法,如果找到,就直接執行,不再搜索
如果找到最后一個類,還沒有找到方法,程序報錯

class A: def test(self): print("A --- test 方法") def demo(self): print("A --- demo 方法") class B: def test(self): print("B --- test 方法") def demo(self): print("B --- demo 方法") class C(B, A): """多繼承可以讓子類對象,同時具有多個父類的屬性和方法""" pass # 創建子類對象 c = C() c.test() c.demo() # 確定C類對象調用方法的順序 print(C.__mro__)
class Dog(object): def __init__(self, name): self.name = name def game(self): print("%s 蹦蹦跳跳的玩耍..." % self.name) class XiaoTianDog(Dog): def game(self): print("%s 飛到天上去玩耍..." % self.name) class Person(object): def __init__(self, name): self.name = name def game_with_dog(self, dog): print("%s 和 %s 快樂的玩耍..." % (self.name, dog.name)) # 讓狗玩耍 dog.game() # 1. 創建一個狗對象 # wangcai = Dog("旺財") wangcai = XiaoTianDog("飛天旺財") # 2. 創建一個小明對象 xiaoming = Person("小明") # 3. 讓小明調用和狗玩的方法 xiaoming.game_with_dog(wangcai)
>>
小明 和 飛天旺財 快樂的玩耍...
飛天旺財 飛到天上去玩耍...
class Father(object): def __init__(self, money): self.money = money def play(self): print("playing") class Mother(object): def __init__(self, food): self.food = food def play(self): print("eatting") #如果多個父類存在重名函數,按照父類參數列表中的順序調用 class Child(Father, Mother): def __init__(self, money, food, score): Mother.__init__(self, food) Father.__init__(self, money) self.score = score f1 = Father(100000) m1 = Mother("apple") c1 = Child(100, "emn...", 59) c1.play() #>>playing print(c1.money)
補充:新式類與舊式(經典)類
object是python為所有對象提供的基類,提供了一些內置屬性和方法,可以使用dir函數查看。
- 新式類:以
object
為基類的類,推薦使用 -
經典類:不以
object
為基類的類,不推薦使用 -
在
Python 3.x
中定義類時,如果沒有指定父類,會 默認使用object
作為該類的 基類 ——Python 3.x
中定義的類都是 新式類 -
在
Python 2.x
中定義類時,如果沒有指定父類,則不會以object
作為 基類
新式類和經典類在多繼承時,會影響到方法的搜索順序
為了保證編寫的代碼能夠同時在 Python 2.x
和 Python 3.x
運行!
今后在定義類時,如果沒有父類,建議統一繼承自 object
class A(object): pass