Python面向對象中的繼承、多態和封裝 一、面向對象的三大特性 1. 封裝:把很多數據封裝到⼀個對象中,把固定功能的代碼封裝到⼀個代碼塊, 函數,對象, 打包成模塊。 這都屬於封裝思想。 2. 繼承:⼦類可以⾃動擁有⽗類中除了私有屬性外的其他所有內容。 說⽩了, ⼉⼦可以隨便⽤爹的東⻄。 3. 多 ...
Python面向對象中的繼承、多態和封裝
一、面向對象的三大特性
- 封裝:把很多數據封裝到⼀個對象中,把固定功能的代碼封裝到⼀個代碼塊, 函數,對象, 打包成模塊。 這都屬於封裝思想。
- 繼承:⼦類可以⾃動擁有⽗類中除了私有屬性外的其他所有內容。 說⽩了, ⼉⼦可以隨便⽤爹的東⻄。
- 多態: 同⼀個對象, 多種形態。在Python中處處是多態,因為在Python中一個變數可以是多種形態。
二、封裝
封裝,顧名思義,就是將某些東西給封裝起來,以後想要使用的時候再去調用。
所以,在使用面向對象的封裝特性時需要:① 將內容封裝到某處 ② 調用時從某處取出來
封裝分為兩部分:
- 廣義上的封裝:實例化一個對象,給對象空間封裝一些屬性。
- 狹義上的封裝:私有制。私有成員:私有靜態屬性,私有方法,私有對象屬性。
我們先看廣義上的封裝:
將內容封裝到某處
class MyClass: def __init__(self, name, age): self.name = name self.age = age # 在實例化對象時,會自動執行__init__方法,將對象空間傳遞給self這個位置參數 obj = MyClass("oldniu", 20) # 將"oldniu" 和 20 分別封裝到obj(self)的name和age屬性中
從某處調用封裝的內容
調用被封裝的內容時,有兩種方式:① 通過對象直接調用 ② 通過self間接調用
通過對象直接調用
class MyClass: def __init__(self, name, age): self.name = name self.age = age obj = MyClass("oldniu", 20) print(obj.name) # 通過obj對象直接調用裡面的name屬性 print(obj.age) # 通過obj對象直接調用裡面的age屬性
通過self間接調用
class MyClass: def __init__(self, name, age): self.name = name self.age = age def func(self): print(self.name) # 使用self間接調用obj對象中的name屬性 print(self.age) # 使用self間接調用obj對象中的age屬性 obj = MyClass("oldniu", 20) obj.func() # 對象執行方法時Python會預設將obj傳給參數self
綜上所述,對於面向對象廣義上的封裝來說,其實就是使用初始化方法將內容封裝到 對象 中,然後通過對象直接或者self間接獲取被封裝的內容。
我們再看狹義上的封裝
私有靜態屬性
class MyClass: __gender = "男" # 私有靜態屬性 def __init__(self, name, age): self.name = name self.age = age def get_gender(self): return self.__gender # 在類的內部可以訪問私有靜態屬性 # print(MyClass.__gender) # 類名不能直接訪問私有靜態屬性 obj = MyClass("oldniu", 20) # print(obj.__gneder) # 對象不能訪問私有靜態屬性 print(obj.get_gender()) # 男
對於私有靜態欄位來說,只能在本類中內部訪問,類的外部,子類均不可訪問。
tips:其實在類的外部可以通過使用_MyClass__gender訪問私有靜態屬性,但是一般我們不會去使用。
私有方法
class MyClass: def __init__(self, name, age): self.name = name self.age = age def __func(self): print("666666") def func(self): self.__func() 類內部訪問靜態方法 obj = MyClass("dogfa", 20) obj.func() # 私有方法和私有靜態欄位一樣,只能在本類中內部訪問,類的外部和子類均不課訪問。
三、繼承
什麼是繼承?⼦類可以⾃動擁有⽗類中除了私有屬性外的其他所有內容就是繼承。
- 繼承的優點
- 提高代碼的復用性
- 提高代碼的維護性
- 使類與類之間發生關聯
- 繼承的分類
- 單繼承
- 多繼承
我們先來看單繼承
類名,對象執行父類方法
# 定義一個Animal父類 class Animal: type_name = "動物類" def __init__(self, name): self.name = name def eat(self): print("吃吃吃...") # 繼承於Animal類 class Dog(Animal): pass # 類名調用父類的屬性和方法 print(Dog.type_name) Dog.eat(111) # 對象調用父類的屬性和方法 dog = Dog("二哈") # 調用父類的__init__方法實例化對象 print(dog.name) print(dog.type_name) dog.eat() print(dog.__dict__)
執行順序
- 實例化對象時必須執行__init__方法,類中沒有,從父類找,父類沒有,從object類中找。
- 先要執行自己類中的eat方法,自己類沒有才能執行父類中的方法。
同時執行類及父類方法
方法一:
如果想執行父類的func方法,這個方法並且子類中夜用,那麼就在子類的方法中寫上:
父類.func(對象,其他參數)
class Animal: type_name = "動物類" def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender def eat(self): print("吃吃吃...") class Bird(Animal): def __init__(self, name, age, gender, wing): Animal.__init__(self, name, age, gender) # 執行了父類的__init__方法進行初始化 self.wing = wing # 給對象封裝wing屬性 obj = Bird("鸚鵡", 3, "雌", "翅膀") print(obj.__dict__) # {'name': '鸚鵡', 'age': 3, 'gender': '雌', 'wing': '翅膀'}
方法二:
利用super,super().func(參數)
class Animal: type_name = "動物類" def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender def eat(self): print("吃吃吃...") class Bird(Animal): def __init__(self, name, age, gender, wing): super().__init__(name, age, gender) # 使用super會自動將self傳給父類 self.wing = wing # 給對象封裝wing屬性 obj = Bird("鸚鵡", 3, "雌", "翅膀") print(obj.__dict__) # {'name': '鸚鵡', 'age': 3, 'gender': '雌', 'wing': '翅膀'}
再來看看多繼承
在多繼承中要補充一點,在這裡要註意類的種類。類可以分為經典類和新式類。
新式類:繼承object類的類稱為新式類。在Python3中預設都是繼承object類,所以預設所有類都是新式類。
經典類:不繼承object類的類稱為經典類。在Python2中預設使用經典類,當然也可以自己手動繼承object類來達到變成經典類的目的。
經典類的多繼承
在Python的繼承體系中, 我們可以把類與類繼承關係化成⼀個樹形結構的圖。
class A: pass class B(A): pass class C(A): pass class D(B, C): pass class E: pass class F(D, E): pass class G(F, D): pass class H: pass class Foo(H, G): pass
對付這種mro畫圖就可以:
繼承關係圖已經有了,那如何進⾏查找呢? 記住⼀個原則, 在經典類中採⽤的是深度優先遍歷⽅案。 什麼是深度優先,就是⼀條路走到頭, 然後再回來, 繼續找下⼀個。
類的MRO: Foo-> H -> G -> F -> E -> D -> B -> A -> C
新式類的繼承
mro序列
MRO是一個有序列表L,在類被創建時就計算出來。
通用計算公式為:mro(Child(Base1,Base2)) = [ Child ] + merge( mro(Base1), mro(Base2), [ Base1, Base2] ) (其中Child繼承自Base1, Base2)
如果繼承至一個基類:class B(A)
這時B的mro序列為mro( B ) = mro( B(A) ) = [B] + merge( mro(A) + [A] ) = [B] + merge( [A] + [A] ) = [B,A]
如果繼承至多個基類:class B(A1, A2, A3 …)
這時B的mro序列mro(B) = mro( B(A1, A2, A3 …) ) = [B] + merge( mro(A1), mro(A2), mro(A3) ..., [A1, A2, A3] ) = ...
計算結果為列表,列表中至少有一個元素即類自己,如上述示例[A1,A2,A3]。merge操作是C3演算法的核心。
表頭和表位
表頭:
列表的第一個元素表尾:
列表中表頭以外的元素集合(可以為空)示例
列表:[A, B, C]
表頭是A,表尾是B和C列表之間的+操作
+操作:
[A] + [B] = [A, B]
(以下的計算中預設省略)
---------------------merge操作示例:
如計算merge( [E,O], [C,E,F,O], [C] ) 有三個列表 : ① ② ③ 1 merge不為空,取出第一個列表列表①的表頭E,進行判斷 各個列表的表尾分別是[O], [E,F,O],E在這些表尾的集合中,因而跳過當前當前列表 2 取出列表②的表頭C,進行判斷 C不在各個列表的集合中,因而將C拿出到merge外,並從所有表頭刪除 merge( [E,O], [C,E,F,O], [C]) = [C] + merge( [E,O], [E,F,O] ) 3 進行下一次新的merge操作 ...... ---------------------
計算mro(A)方式:
mro(A) = mro( A(B,C) ) 原式= [A] + merge( mro(B),mro(C),[B,C] ) mro(B) = mro( B(D,E) ) = [B] + merge( mro(D), mro(E), [D,E] ) # 多繼承 = [B] + merge( [D,O] , [E,O] , [D,E] ) # 單繼承mro(D(O))=[D,O] = [B,D] + merge( [O] , [E,O] , [E] ) # 拿出並刪除D = [B,D,E] + merge([O] , [O]) = [B,D,E,O] mro(C) = mro( C(E,F) ) = [C] + merge( mro(E), mro(F), [E,F] ) = [C] + merge( [E,O] , [F,O] , [E,F] ) = [C,E] + merge( [O] , [F,O] , [F] ) # 跳過O,拿出並刪除 = [C,E,F] + merge([O] , [O]) = [C,E,F,O] 原式= [A] + merge( [B,D,E,O], [C,E,F,O], [B,C]) = [A,B] + merge( [D,E,O], [C,E,F,O], [C]) = [A,B,D] + merge( [E,O], [C,E,F,O], [C]) # 跳過E = [A,B,D,C] + merge([E,O], [E,F,O]) = [A,B,D,C,E] + merge([O], [F,O]) # 跳過O = [A,B,D,C,E,F] + merge([O], [O]) = [A,B,D,C,E,F,O] ---------------------
三、多態
多態:同一個對象,多種形態。
由於Python是弱類型語言, 在定義變數和傳參的時候可以是任意形態的值,所以Python預設支持多態。
鴨子類型:你看起來像鴨子,那就是鴨子。
對相同的功能設定了相同的名字,這樣方便開發,這兩個方法就可以互成為鴨子類型。比如list、tuple、str都有index()方法,這就是互稱為鴨子類型。