python3基礎篇(八)——面向對象
前言
1 閱讀這篇文章我能學到什麼?
這篇文章將爲你詳細介紹python3中的面向對象程序設計,你將學會如何定義一個類和使用一個對象。
——如果你覺得這是一篇不錯的文章,希望你能給一個小小的贊,感謝您的支持。
目錄
python3是一門面向對象的語言,掌握好面向對象的程序設計是用好python3的前提。如果你理解c++的“萬物皆對象”概念那學習起來就輕鬆很多了。
面向對象是一個抽象的程序設計概念,是軟件開發的方法。起初它來自於程序設計,而如今面向對象概念已經擴展到數據庫系統、交互結構、應用結構、應用平臺、分佈式系統、網絡管理結構、CAD計數、人工智能等領域。這種思想就像種子一樣已經到處傳播併發芽生長。
對象編程將任何事物(可以是客觀存在或抽象的,客觀的比如桌子板凳,抽象的比如時間和愛情)都看做具有 屬性 和 方法 的對象,又從同一類事物中抽象出他們的 共性 (比如人類共有的屬性是年齡和體重,共有的方法是學習和吃飯)形成類。這就是 面向對象 的概念,也是對象和類的關係。
——作者:這段話是我自己總結的,如有不對歡迎指正
通過上面的描述我們知道了對象是描述事物的,而類是對象的共性的抽象。 面向對象編程 (注意區分面向對象和麪向對象編程)就是以操控對象的形式去編程,要創建對象就必須爲對象定義相應的類,類描述了對象的屬性和方法。
1 定義帶有屬性和方法的簡單類
python3中使用關鍵字class
定義類,後面接類名,不要忘了後面的:
符號,類主體至少要比class縮進一個空格或table。類的主體中定義屬性和方法。
屬性是描述對象的共性特徵的,比如年齡和身高,對應編程語言中存儲數據的變量。方法是對象能完成的動作,比如“我”可以寫博客也可以吃飯,對應編程語言能完成特定功能的函數。 面向對象的三大特徵是封裝、繼承、多態 ,其中封裝和繼承主要是爲了增加代碼的重用性,多態主要是爲了以不變的代碼應對萬變的需求。
下面我們來設計一個類——Human,Human的共性就是都具有年齡和身高,年齡會長大而身高會長高。當然還具有很多其他共性,我們不可能一一寫出來,只需要設計出我們關心的幾個共性。
代碼示例:
class Human: #定義類,也可寫爲class Human()
"對類的描述" #描述可省略
def __init__(self):
#類的成員變量)
self.Age = 18
self.Height = 183
#類中的方法(類的成員函數)
def AddHeight(self):
self.Height += 1
return self.Height
def AddAge(self):
self.Age += 1
return self.Age
Calm = Human() #將類實例化成對象Calm
print(Calm.Height) #訪問對象的屬性Height
print(Calm.Age) #訪問對象的屬性Age
print(Calm.AddHeight()) #調用對象的方法
print(Calm.AddAge()) #調用對象的方法
運行結果:
183
18
184
19
類中定義了屬性Age
和Height
,並設定了初始值18和183,AddHeight
和AddAge
爲類的方法。類定義好後Calm = Human()
這句代碼即實現將描述對象共性的類實例化出一個具體的對象Calm
,這就像工廠裏的模具能取出形狀大小一樣的物件一樣。我們說過面向對象編程是把事物都看做具有屬性和方法的對象,面向對象編程是通過操控的形式編程。現在有了對象Calm,我們就可以通過它去訪問對象Calm的屬性和方法了。通過成員運算符.
即可訪問到該對象下的屬性和方法。
上面代碼裏的self
是什麼作用?python3規定,類中的方法區別於普通函數,類方法參數列表的第一個參數(python3並沒有規定第一個參數名只能是self
,只要符合命名規則即可,用self
是大家通用的習慣)必須用於接收對象自己。這樣做有三個目的:
- 從寫法上區別於普通函數。
- 將函數和對象綁定在一起,這樣就清楚這個函數對應哪個對象了。
- 方便在類方法中操作類中的其他屬性和方法。
爲什麼說self
就是對象自己呢?我們寫一段簡單的代碼輸出看一下對象地址,輸出看一下self
就知道了
代碼示例:
class cTest:
def Function(self):
print(self)
Test1 = cTest()
Test1.Function() #print(self)
print(Test1) #print(Test1)
Test2 = cTest()
Test2.Function() #print(self)
print(Test2) #print(Test2)
運行結果:
<__main__.cTest object at 0x0000010DDA0C7400>
<__main__.cTest object at 0x0000010DDA0C7400>
<__main__.cTest object at 0x0000010DDA142CA0>
<__main__.cTest object at 0x0000010DDA142CA0>
從上面代碼可以看出對象Test1
和Test2
是兩個不同的對象,因此具有不同的對象地址(對應計算機上存儲空間不同),但是它們各自的self
是完全等價於對象自己的。就類似於c++和java中的this指針。
明白了self
後現在我們回到Human
這個類的話題。AddHeight
和AddAge
的第一參數是self
即表明了它們是類的成員方法,同時將成員方法和對象關聯起來,在成員方法拿到對象實例後就可以在成員方法內部訪問對象的屬性和方法。所以self.Height
和self.Age
是成員方法通過對象自己的實例訪問了自己的屬性。並且有了self
將對象和方法關聯起來,100個Human
對象self.Age
訪問的都是自己獨立的屬性Age
。(各有各的年齡)。
代碼示例:
class Human: #定義類
"對類的描述" #描述可省略
def __init__(self):
#類的成員變量)
self.Age = 18
self.Height = 183
#類中的方法(類的成員函數)
def AddHeight(self):
self.Height += 1
return self.Height
def AddAge(self):
self.Age += 1
return self.Age
Calm = Human() #將類實例化成對象Calm
XiaoMing = Human()
print(Calm.Age)
print(XiaoMing.Age)
print("-------------------------------")
XiaoMing.Age = 19 #修改了小明的年齡爲19,但是不會影響Calm的18歲年齡
print(Calm.Age)
print(XiaoMing.Age)
運行結果:
18
18
-------------------------------
18
19
2 類的構造函數
我們從Human
類創建了兩個對象Calm
和XiaoMing
,它們的年齡都是18歲。如果我們要實現這樣一個功能,對象創建時默認初始年齡是0歲並且具有初始升高(這更符合Human
類的特點)。這就涉及到構造函數的概念。構造函數是一種特殊的類內方法,在對象被創建(實例化)時構造函數自動被調用,它的主要作用是對對象的成員變量進行初始化賦值。python3的構造函數名固定爲__init__
。構造函數可以省略也可被用戶重載,省略時作用相當於沒有參數和函數體的無參構造函數。
2.1 類的無參構造函數
代碼示例:
class cTest:
#重載無參構造函數,具有函數體
def __init__(self):
#午餐構造函數的函數體
print("call: __init__(self)")
self.Parameter = 0
print(self.Parameter)
Object1 = cTest()
運行結果:
call: __init__(self)
0
當一個類沒有__init__
構造函數時,系統會默認調用無參無函數體的構造函數。當用戶重載__init__
構造函數時,創建對象時將會調用用戶定義後的構造函數。
2.2 類的帶參構造函數
構造函數重載後可帶參,類實例化爲對象時參數列表按照帶參構造函數參數列表。
class cTest:
#重載無參構造函數
def __init__(self, Parameter1, Parameter2):
#午餐構造函數的函數體
print("call: __init__(self, Parameter1, Parameter2)")
self.Parameter = Parameter1 * Parameter2
print(self.Parameter)
Object1 = cTest(2, 5)
運行結果:
call: __init__(self, Parameter1, Parameter2)
10
2.3 構造函數與普通成員函數區別
- 構造函數函數名只能是__init__,而普通成員函數函數名完全有用戶定義。
- 普通程序函數定義後才存在,未定義也就不存在,而每個類都自帶一個默認構造函數(作用相當於無參無函數體構造函數),可由用戶重載。
- 普通成員函數需要用戶主動去調用,而構造函數在對象創建(實例化)時自動調用。普通成員函數可以被調用多次,而構造函數只在對象創建時自動調用一次。
- 用法上普通成員函數一般用於實現邏輯,構造函數一般用於對成員變量進行初始化。
- 數量上一個類可以有多個普通成員函數,但只能有一個構造函數。
2.4 構造函數的多態
在c++和Java裏類中構造函數可以有多個,只要他們參數列表不同(參數個數、參數順序、參數類型)即可。而python3中類的構造函數只能有一個。面向對象的三大特徵是封裝、繼承、多態。python3的一個構造函數怎麼實現構造函數的多臺呢?
我們知道python3的變量是不分類型的,這一點可以實現不同參數類型的構造函數以及參數順序不同的構造函數。其次python3的函數參數是可以帶默認值的,這一點可以實現參數個數不同的構造函數。另外python3的函數支持變長參數,這一點也可以實現不同參數個數的構造函數。這些特性支撐起了python3構造函數的多態性。
2.4.1 構造函數多態——參數類型不一致
代碼示例:
class cTest:
def __init__(self, Parameter):
self.Parameter = Parameter
print(self.Parameter)
#不同參數類型的構造函數
Object1 = cTest(2) #Number
Object2 = cTest("a") #String
Object3 = cTest((1, 2)) #Tuple
Object4 = cTest([1, 2]) #List
Object5 = cTest({1, 2}) #Set
Object6 = cTest({"a":1, "b":2}) #Dictionary
運行結果:
2
a
(1, 2)
[1, 2]
{1, 2}
{'a': 1, 'b': 2}
根據python3變量沒有類型的特性,實現了構造函數的不同參數類型下的多態。
2.4.2 構造函數多態——參數順序不一致
代碼示例:
class cTest:
def __init__(self, Parameter1, Parameter2):
self.Parameter1 = Parameter1
self.Parameter2 = Parameter2
print(self.Parameter1, self.Parameter2)
Object1 = cTest(1, "a") #Num String
Object2 = cTest("a", 1) #String Num
運行結果:
1 a
a 1
根據python3變量沒有類型的特性,實現了構造函數的不同參數類型順序下的多態。
2.4.3 構造函數多態——參數個數不一致
代碼示例:
class cTest:
def __init__(self, *Parameter): #元組形式不定長參數
self.Parameter = Parameter
print(self.Parameter)
Object1 = cTest(1) #1個參數
Object2 = cTest(1, "a") #2個參數
Object3 = cTest(1, "a", 2, 3) #4個參數
運行結果:
(1,)
(1, 'a')
(1, 'a', 2, 3)
根據python3變量沒有類型的特性,實現了構造函數的不同參數數量下的多態。
因此python3實現構造函數的多態性並不需要像c++和java那樣一個類重載多個構造函數,它只需要一個構造函數就夠了。
我們繼續前面的Human類的例子。我們利用構造函數實現每個Human對象產生後Age初始爲0,並且具有初始Height。
代碼示例:
class Human: #定義類
#類的構造函數
def __init__(self, Height):
self.Age = 0
self.Height = Height
#類中的方法(類的成員函數)
def AddHeight(self):
self.Height += 1
return self.Height
def AddAge(self):
self.Age += 1
return self.Age
Calm = Human(50) #將類實例化成對象Calm
print(Calm.Age, Calm.Height)
運行結果:
0 50
3 類的析構函數
python3中的變量都可被主動(使用del關鍵字)或被動(作用域結束)釋放(回收內存空間),當對象被釋放時會自動調用一次析構函數。每個類都有一個析構函數,當用戶未顯式定義時會調用默認的析構函數,否則會調用用戶重載的析構函數。默認析構函數作用等同於無函數體的析構函數。析構函數的函數名是固定的即__del__
代碼示例:
class cTest:
def __init__(self):
print("__init__")
def __del__(self): #對象被釋放時自動調用
print("__del__")
Object = cTest()
print("--------------------------")
del Object #釋放對象,觸發調用析構函數
#print(Object.Parameter) #error 此時Object對象已經不存在了,不能繼續使用
運行結果:
__init__
--------------------------
__del__
請勿給析構函數定義除了self
外的其他參數。
上面講了python3的構造函數和析構函數,它們有其特殊性,但是如果你通過對象主動去調用也不會報錯,構造函數和析構函數會像普通函數那樣執行。但是不建議這樣操作。
代碼示例:
class cTest:
def __init__(self):
print("__init__")
self.Parameter = 1
def __del__(self): #對象被釋放時自動調用
print("__del__")
Object = cTest()
print("--------------------------")
Object.__init__() #主動調用構造函數不會讓對象被重新創建
Object.__del__() #主動調用析構函數不會讓對象被釋放
print(Object.Parameter) #對象沒有被釋放,此時操作對象依舊有效
運行結果:
__init__
--------------------------
__init__
__del__
1
__del__
可以看到最後又輸出了一次__del__
,這是因爲程序運行結束,超出了對象的作用域範圍因此引起對象被自動釋放從而調用了析構函數。
4 類中的變量
類內的變量可以分爲三類,一類是在類中但不在方法內的變量,一類是在構造函數內的,一類是在普通成員函數內的。
4.1 類內變量
我們把在類中,但不在方法內的變量稱爲類內變量。類內變量初始化定義在類中,定義時不需要通過self
將其與對象關聯起來。類定義後類內變量即存在,可以通過類訪問,即使類還沒有實例化任何對象。也可通過對象訪問類內變量,當對象沒有定義自己的類內變量時訪問的都是類的類內變量,也即每個對象可以擁有自己獨立的類內變量,但這需要在定義後否則默認都是類的類內變量。
class cTest:
Parameter1 = 1 #Parameter1是類變量,不需要通過self調用
def __init__(self):
pass
Object1 = cTest()
print(cTest.Parameter1) #類內變量通過類訪問
print(Object1.Parameter1) #類內變量通過對象訪問
print("---------------1------------------")
#在對象未定義類內變量時,訪問對象的類內變量會自動訪問類的類內變量
Object2 = cTest()
print(Object2.Parameter1)
cTest.Parameter1 = 10 #通過類名修改類內變量
print(cTest.Parameter1)
print(Object1.Parameter1)
print(Object2.Parameter1)
print("---------------2-----------------")
#給對象創建類內變量後其就具有了獨立的類內變量,值不再等於類的類內變量
Object1.Parameter1 = 20 #通過對象修改類內變量,也即給對象Object1創建類內變量
print(cTest.Parameter1)
print(Object1.Parameter1)
print(Object2.Parameter1)
print("---------------3-----------------")
cTest.Parameter1 = 30 #通過類名修改類內變量
print(cTest.Parameter1)
print(Object1.Parameter1)
print(Object2.Parameter1)
print("---------------4----------------")
Object2.Parameter1 = 40 #通過對象修改類內變量,也即給對象Object2創建類內變量
print(cTest.Parameter1)
print(Object1.Parameter1)
print(Object2.Parameter1)
print("---------------5----------------")
#對象的類內變量刪除後,訪問對象的類內變量即訪問類的類內變量
del Object1.Parameter1 #刪除對象的類內變量
del Object2.Parameter1 #刪除對象的類內變量
print(cTest.Parameter1)
print(Object1.Parameter1)
print(Object2.Parameter1)
運行結果:
1
1
---------------1------------------
1
10
10
10
---------------2-----------------
10
20
10
---------------3-----------------
30
20
30
---------------4----------------
30
20
40
---------------5----------------
30
30
30
4.2 成員變量
成員變量爲定義在構造函數內的變量,它需要通過self
將其與對象關聯起來使得可以通過對象進行訪問。因爲定義在構造函數內,因此成員變量在對象創建後(也即構造函數調用後)即存在。每個對象都具有自己獨立的一份成員變量。
代碼示例:
class cTest:
def __init__(self):
self.Parameter1 = 1 #置於構造函數內,對象被創建後即存在
Parameter2 = 2 #置於構造函數內,對象被創建後即存在,但構造函數只能調用一次,這樣定義沒有價值
Object1 = cTest() #構造函數調用後成員變量即存在
print(Object1.Parameter1) #訪問成員變量。因爲使用了self將其與特定對象關聯,因此可以通過對此昂訪問
#print(Object1.Parameter2) #error 函數內的局部變量,定義時未使用self與對象關聯因此無法通過對象訪問
Object2 = cTest()
print(Object2.Parameter1)
print("------------------------------")
Object1.Parameter1 = 0 #修改對象Object1的成員變量,不會引起其他對象成員變量的改變
print(Object1.Parameter1)
print(Object2.Parameter1)
運行結果:
1
1
------------------------------
0
1
4.2 成員函數內局部變量
成員函數內局部變量和普通函數內局部變量類似。不同在於類的成員函數內局部變量可被self
關聯到對象,使得能通過對象訪問。
代碼示例:
class cTest:
def Function(self):
self.Parameter1 = 1 #局部變量被self關聯到對象
Parameter2 = 2 #未通過self關聯到對象
Object1 = cTest()
Object1.Function() #成員函數調用後成員函數內局部變量即存在
print(Object1.Parameter1) #訪問成員變量。因爲使用了self將其與特定對象關聯,因此可以通過對象訪問
#print(Object1.Parameter2) #error 函數內的局部變量,定義時未使用self與對象關聯因此無法通過對象訪問
Object2 = cTest()
Object2.Function()
print(Object2.Parameter1)
print("------------------------------")
Object1.Parameter1 = 0 #修改對象Object1的成員變量,不會引起其他對象成員變量的改變
print(Object1.Parameter1)
print(Object2.Parameter1)
運行結果:
1
1
------------------------------
0
1
類成員函數內的局部變量在成員函數調用後存在。不同的類擁有自己獨立的成員函數,也相應是獨立的成員函數內局部變量。被self
修飾後的局部變量可被子類繼承。
5 類的繼承
類的繼承描述的是一種子類和父類之間的關係。它是面向對象的一個重要概念,目的是增強代碼的重用性。繼承是在已有類的基礎上創建新的類,已有類稱爲基類(也稱父類、超類),新類稱爲派生類(也稱子類)。派生類可以使用基類中定義的公有型成員變量或成員函數(私有屬性和方法不能不能被子類繼承)。一個派生類可以只繼承自一個基類,稱爲單繼承,也可以繼承自多個基類,稱爲多繼承。
語法上,類定義時在類名後()
內的類名即表示該類繼承自的基類,只寫一個基類時是單繼承,多個則爲多繼承,沒有時可省略空的()
。
5.1 單繼承
我們嘗試定義三個類,分別是:Human
、Man
、Woman
,用Human
作爲Man
和Woman
的基類。試着通過派生類去訪問父類的方法和屬性。
代碼示例:
class Human: #定義基類
def __init__(self, Name, Height): #Human的構造函數
print("Call Human:__init__")
self.Name = Name
self.Age = 0
self.Height = Height
#類中的方法(類的成員函數)
def AddHeight(self):
self.Height += 1
return self.Height
def AddAge(self):
self.Age += 1
return self.Age
class Man(Human):
def __init__(self, Name, Height):
print("Call Man:__init__")
self.Gender = "Man" #性別爲男
Human.__init__(self, Name, Height) #調用父類構造函數。注意第一個參數是self,表示將子類對象傳給父類
#打獵
def Hunting(self):
print("hunting")
#談論年齡和身高
def Speak(self):
print(self.Age, self.Height) #派生類成員方法中調用基類的屬性
#成長
def GrowUp(self):
Human.AddAge(self) #派生類成員方法中調用基類成員方法
Human.AddHeight(self)
class Woman(Human):
def __init__(self, Name, Height):
print("Call Man:__init__")
self.Gender = "Woman"
super().__init__(Name, Height) #調用父類構造函數。第一個參數不需是self
def Pick(self):
print("pick")
# 談論年齡和身高
def Speak(self):
print(self.Age, self.Height) #派生類成員方法中調用基類的屬性
# 成長
def GrowUp(self):
super().AddAge() #派生類成員方法中調用基類成員方法
super().AddHeight()
Calm = Man("Calm", 50) #實例化Man對象
print("---------------------------")
XiaoHua = Woman("XiaoHua", 48) #實例化Woman對象
print("---------------------------")
print(Calm.Age, Calm.Height) #派生類成員調用繼承自基類的屬性
Calm.AddAge() #派生類成員調用繼承自基類的方法
Calm.AddHeight()
print(Calm.Age, Calm.Height)
print("---------------------------")
print(Calm.Gender) #派生類調用自己的屬性
print(XiaoHua.Gender)
Calm.Hunting() #派生類調用自己特有的方法
XiaoHua.Pick()
print("---------------------------")
Calm.GrowUp()
Calm.Speak()
print("---------------------------")
XiaoHua.GrowUp()
XiaoHua.Speak()
運行結果:
Call Man:__init__
Call Human:__init__
---------------------------
Call Man:__init__
Call Human:__init__
---------------------------
0 50
1 51
---------------------------
Man
Woman
hunting
pick
---------------------------
2 52
---------------------------
1 49
派生類可以繼承所有基類的公有屬性和方法。通過派生類對象訪問基類的屬性和方法可直接 派生類對象名.基類屬性名/方法名 ,與訪問派生類自己的方法和屬性語法一致。在派生類的方法中訪問基類的屬性可通過 self.基類屬性名 ,與派生類訪問自己的屬性一致。在派生類中訪問基類的方法時有兩種形式, 基類類名.基類方法名 ,但是需要注意這種形式基類方法調用參數列表的第一個參數必須是傳遞基類對象自身,也即第一個參數需要爲self
,另外一種形式是 super().基類方法名 ,這種形式參數列表裏不需要self
,super是python3的內置函數,單繼承推薦這種寫法,簡潔。
在派生類對象產生是會自動調用派生類的構造函數,而基類的構造函數需要在派生類的構造函數裏手動顯式調用。
5.2 多繼承
當一個l類有多個基類時,派生類和多個基類之間就形成了多繼承的關係。派生類可以繼承基類全部的全局屬性和方法。
代碼示例:
class cA:
ParameterA1 = 1
def __init__(self):
print("call A __init__")
self.ParameterA2 = 2
def FunctionA(self):
self.ParameterA3 = 3
class cB():
ParameterB1 = 1
def __init__(self):
print("call B __init__")
self.ParameterB2 = 2
def FunctionB(self):
self.ParameterB3 = 3
class cC(cA, cB):
ParameterC1 = 1
def __init__(self):
#super().__init__() #該函數只能調用類繼承列表裏的第一個類(即cA)的構造函數
cA.__init__(self)
cB.__init__(self)
print("call C __init__")
self.ParameterC2 = 2
def FunctionC(self):
self.ParameterC3 = 3
Object = cC()
print(Object.ParameterA1)
print(Object.ParameterB1)
print(Object.ParameterC1)
print("-------------------------------")
print(Object.ParameterA2)
print(Object.ParameterB2)
print(Object.ParameterC2)
print("-------------------------------")
Object.FunctionA()
Object.FunctionB()
Object.FunctionC()
print(Object.ParameterA3)
print(Object.ParameterB3)
print(Object.ParameterC3)
運行結果:
call A __init__
call B __init__
call C __init__
1
1
1
-------------------------------
2
2
2
-------------------------------
3
3
3
基類構造函數調用父類構造函數時,由於多繼承有多個父類構造函數因此調用基類的構造函數需要使用 類名.構造函數名 形式,構造函數參數列表第一個參數必須是傳遞對象本身即self
。派生類對象可以使用所有基類的公有屬性和方法,當然派生類自己的成員方法和屬性也可以調用。派生類的構造函數裏分別調用了基類cA
和cB
的構造函數。
5.3 多級繼承
如果存在A、B、C三個類,B繼承於A,C繼承於B,那麼實例化一個C類的對象,這個對象擁有C類全部的屬性和方法,並且具有A和B全部的公有屬性和方法。
代碼示例:
class cA:
ParameterA1 = 1
def __init__(self):
print("call A __init__")
self.ParameterA2 = 2
def FunctionA(self):
self.ParameterA3 = 3
class cB(cA):
ParameterB1 = 1
def __init__(self):
print("call B __init__")
super().__init__()
self.ParameterB2 = 2
def FunctionB(self):
self.ParameterB3 = 3
class cC(cB):
ParameterC1 = 1
def __init__(self):
print("call C __init__")
super().__init__()
self.ParameterC2 = 2
def FunctionC(self):
self.ParameterC3 = 3
Object = cC()
print(Object.ParameterA1)
print(Object.ParameterB1)
print(Object.ParameterC1)
print("-------------------------------")
print(Object.ParameterA2)
print(Object.ParameterB2)
print(Object.ParameterC2)
print("-------------------------------")
Object.FunctionA()
Object.FunctionB()
Object.FunctionC()
print(Object.ParameterA3)
print(Object.ParameterB3)
print(Object.ParameterC3)
運行結果:
call C __init__
call B __init__
call A __init__
1
1
1
-------------------------------
2
2
2
-------------------------------
3
3
3
實例化類cC
的對象時,自動調用類cC
的構造函數,在類cC
的構造函數裏又調用了基類cB
的構造函數,然後在類cB
的構造函數裏又調用了類cA
的構造函數。多級繼承就是這樣層層找基類。與多繼承類似,類cC
的對象object
可以使用類cC
的全部方法和屬性,且繼承了類cB
和類cA
的全部公有屬性和方法。
6 方法重載(覆寫)
方法重載不止限於類中,普通函數也可以被重載。c++中兩個函數函數名相同但是參數列表不同會被認爲是不同的函數,可以同時存在,但是在python3只要函數名相同就被認爲是同一個函數,並且最終調用會指向最新一次的函數定義。
6.1 普通函數重載
代碼示例:
def Function1(n):
print("Function A")
def Function1(n, m): #重載了Function1並且參數列表不同
print("Function B")
def Function2():
print("Function C")
def Function2(): #重載了Function2
print("Function D")
#Function1(1) #error 一個參數的方法已被重載
Function1(1, 2)
Function2()
運行結果:
Function B
Function D
Function2
被定義了兩次,第二次會覆蓋第一次定義。Function1
雖然兩次定義參數列表不同,但是函數名相同,第二次定義依然會覆蓋第一次定義。總之python3以函數名作爲函數區分,重複定義相同的函數名會將原來的實現覆蓋。
6.2 類方法重載
類具有繼承性,因此關於類的方法重載有兩種情況,一種是重載自己的方法,一種是重載父類的方法。
6.2.1 類重載自己的方法
與普通函數重載類似。
代碼示例:
class cA:
def Function1(self): #Function第一次定義
print("Function1 1")
def Function1(self): #重載了舊的定義
print("Function1 2")
def Function2(self, n):
print("Function2 1")
def Function2(self, n, m):
print("Function2 2")
Object = cA()
Object.Function1()
#Object.Function1(1) #error 一個參數的方法已被重載
Object.Function2(1, 2)
運行結果:
Function1 2
Function2 2
函數名相同,新的定義覆蓋了舊的定義。
6.2.2 類重載基類的方法和屬性
派生類可以繼承基類的公有方法,可以在派生類中對基類的公有方法進行重載。基類方法被派生類重載後,派生類對象仍然可以通過super
內置函數訪問到基類的被重載方法。類中除了方法還具有屬性,派生類可以通過定義同名屬性來重載基類的屬性。
代碼示例:
class cA:
Parameter1 = 1
def __init__(self):
self.Parameter2 = 1
def Function1(self):
print("cA Function1")
self.Parameter3 = 1
def Function2(self, n):
print("cA Function2")
class cB(cA):
Parameter1 = 2
def __init__(self):
super().__init__()
self.Parameter2 = 2
def Function1(self):
print("cB Function1")
self.Parameter3 = 2
def Function2(self, n, m):
print("cB Function2")
Object = cB()
Object.Function1() #派生類重載了基類的方法,此時調用的是基類重載後的方法
print(Object.Parameter1)
print(Object.Parameter2)
print(Object.Parameter3)
print("------------------------")
Object.Function2(1, 2) #此時調用的是基類重載後的方法
print("------------------------")
super(cB, Object).Function2(2) #通過super調用到了重載前的基類方法
print(super(cB, Object).Parameter1) #通過super函數訪問到了基類的類內變量
#print(super(cB, Object).Parameter2) #不能通過super訪問到基類的成員變量
#print(super(cB, Object).Parameter3) #不能通過super訪問到基類的
運行結果:
cB Function1
2
2
2
------------------------
cB Function2
------------------------
cA Function2
1
6.3 類的運算符重載
和c++一樣,python3的運算符也支持重載,方便成員的運算。python3爲類提供了一些專有的方法,它們可被重載。
常見的類運算符重載方法:
函數名 | 含義 |
---|---|
init | 構造函數 |
del | 析構函數 |
repr | 打印轉換 |
len | 獲取長度 |
cmp | 比較運算 |
call | 函數調用 |
add | 加運算 |
sub | 減運算 |
truediv | 除運算 |
mod | 求餘運算 |
pow | 乘方 |
類的內置函數還有很多,以上只是常用的,它們都可以被重載,即用戶去定義實現規則。
以下代碼展示了對象之間的運算
代碼示例:
class Arithmetic:
def __init__(self, Num):
self.Num = Num
def __str__(self): #重載對象的打印輸出
return "Num: %d" % (self.Num)
def __add__(self, other): #重載兩個對象加運算
print("__add__")
return self.Num + other.Num
def __sub__(self, other): #重載兩個對象差運算
print("__sub__")
return self.Num - other.Num
def __mul__(self, other):
print("__mul__")
return self.Num * other.Num
def __truediv__(self, other):
print("__mul__")
return self.Num / other.Num
Object1 = Arithmetic(10)
Object2 = Arithmetic(2)
print(Object1) #打印對象
print(Object2) #打印對象
print(Object1 + Object2)
print(Object1 - Object2)
print(Object1 * Object2)
print(Object1 / Object2)
運行結果:
Num: 10
Num: 2
__add__
12
__sub__
8
__mul__
20
__mul__
5.0
當類的內置方法被重載後,其對象進行的相關運算會調用類重載運算符後的方法進行運算。
以下代碼展示了對象與數值之間的運算。
代碼示例:
class Arithmetic:
def __init__(self, Num):
self.Num = Num
def __str__(self): #重載對象的打印輸出
return "Num: %d" % (self.Num)
def __add__(self, Num): #重載對象與數值加運算
print("__add__")
return self.Num + Num
Object = Arithmetic(10)
print(Object + 3) #對象與數值相加
運算結果:
__add__
13
7 類的私有屬性和方法
python3中屬性或方法名前加上兩個下劃線_
前綴,聲明該屬性或方法爲私有。私有屬性或方法不能再類的外部被使用或直接訪問。私有屬性和方法不能被繼承。未加雙_
的爲公有屬性或方法。
7.1 類中私有屬性和方法
代碼示例:
class cTest:
__Parameter1 = 1
def __init__(self):
self.__Parameter2 = 2
def Function1(self):
self.__Parameter3 = 3
def __Function2(self):
print("__Function2")
def Function3(self): #公有方法
self.__Function2()
print(self.__Parameter1) #成員方法訪問私有類內變量
print(self.__Parameter2) #成員方法訪問私有成員變量
print(self.__Parameter3) #成員方法訪問私有函數內局部變量
Object = cTest()
#print(Object.__Parameter1) #error 私有類內變量不能被外部訪問
#print(cTest.__Parameter1)
#print(Object.__Parameter2) #error 私有成員變量不能被外部訪問
Object.Function1()
#print(Object.__Parameter3) #error 私有成員函數內局部變量不能被外部訪問
#Object.__Function2() #error 私有成員方法不能被外部訪問
Object.Function3() #通過公有成員函數去間接訪問私有屬性和方法
運行結果:
__Function2
1
2
3
雖然私變量不能被外部訪問,但是可以通過公有方法去間接訪問私有屬性或方法。
7.2 類繼承時的私有屬性和方法
公有的屬性(函數內部未被self修飾的局部變量不能被繼承)和方法可被子類繼承。
代碼示例:
class cA:
__Parameter1 = 1
def __init__(self):
self.__Parameter2 = 2
def Function1(self):
self.__Parameter3 = 3
def __Function2(self):
print("__Function2")
def Function3(self): #公有方法
self.__Function2()
print(self.__Parameter1) #成員方法訪問私有類內變量
print(self.__Parameter2) #成員方法訪問私有成員變量
print(self.__Parameter3) #成員方法訪問私有函數內局部變量
class cB(cA):
pass
Object = cB()
#print(Object.__Parameter1) #error 私有類內變量不能被繼承
#print(cB.__Parameter1)
#print(Object.__Parameter2) #error 私有成員變量不能繼承
Object.Function1()
#print(Object.__Parameter3) #error 私有成員函數內局部變量不能被繼承
#Object.__Function2() #error 私有成員方法不能被繼承
Object.Function3() #通過繼承到的公有成員函數去間接訪問繼承到的私有屬性和方法
運行結果:
__Function2
1
2
3