【python系統學習13】類(class)與對象(object)
- 2020 年 4 月 8 日
- 筆記
目錄:
類(class)和實例
類
整數、字元串、浮點數等,不同的數據類型就屬於不同的類。
想想看當初學數據類型,我們用type驗證數據類型後列印的結果
忘了就再來看看:
strs = '字元串'
ints = 1
floats = 2.3
print(type(strs)) # <class 'str'>
print(type(ints)) # <class 'int'>
print(type(floats)) # <class 'float'>
以上,class就是類。
顧名思義class ‘str’就表示是字元串類。
同理,剩下倆個就是整數類、浮點數類…
「
類之所以為類,是因為每一個類之下都包含無數相似的不同個例。
類,是對某個群體的統稱。 比如:人類、犬類」
實例
「
在Python的術語里,我們把類的個例就叫做實例 (instance),可理解為「實際的例子」。
」
比如上邊程式碼中的’字元串’、1、2.3。這三個就分別是字元串類的實例、整數類的實例、浮點數類的實例。
「
類是某個群體,實例是群體中某個具體的個體。
群體里的每個個體都有著相同/相似的特徵和行為。也就是說實例之間會有相同/相似的特徵和行為。」
比如,字元串中的實例舉幾個例子:
str1 = '我是字元串'
str2 = '1'
str3 = '2.3'
str4 = 'True'
以上四個都是字元串,因為他們都是用英文引號包裹。這就是他們的相同特徵。
即使他們四個的內容值完全不一樣。
而也因此,值不一樣就是他們實例之間的區別、是區別於其他實例個體的特徵。
小測試
請問:狗、秋田犬、忠犬八公、list、[1,2]
以上這五個元素,哪個是類、哪個是實例?
答案見源碼同目錄下files中同名py文件
對象(object)
「
佛說:萬事萬物,皆可為對象。
」
咳咳,佛說,我說的不是男女對象那個對象!
「
這裡所謂Python中的對象,等於類和實例的集合:類可以看作是對象,實例也可以看作是對象。
」
比如列表list是個類對象,[1,2]是個實例對象,它們都是對象。
再比如說人類是個類對象,也可以說小紅是個實例對象(這裡小紅依舊不是你的女對象!!!清醒點!!螞蟻競走了十年了!!!)。
屬性和方法
區別於其他類的依據,細分可以分成兩種:
-
第一種是描述事物是怎樣的,有什麼特徵 – 這就是所說的【屬性】 -
第二種是描述事物能做什麼,有哪些行為和作用 – 也就是所說的【方法】
Python中每個類都有自己獨特的屬性(attribute)和方法(method),是這個類的所有實例都共享的。換言之,每個實例都可以調用類中所有的屬性和方法。
不過各個類的屬性和方法,是需要我們自行創建的。除了python中已有的數據類型其屬性和方法是內置建好的。
比如:列表的內置方法有append、pop等。而這些方法任何列表實例值都可以使用
listObject = [1,3,'列表實例里的第三個元素'] # 一個列表實例
listObject.append('我是列表實例利用類上的append方法添加進來的元素') # 調用列表類的內置方法append
print(listObject) # [1, 3, '列表實例里的第三個元素', '我是列表實例利用類上的append方法添加進來的元素']
類的創建
上節,函數用def
關鍵字定義。
本節,類的創建用class
關鍵字定義。
偽程式碼
class 首字母大寫的類變數名:
自定義屬性名 = 屬性值
def 自定義方法名(self,參數1,可以沒有參數2):
方法函數體內容
具體的含義:
-
用 class
關鍵字創建,class+類名+英文冒號 -
類名首字母大寫,是自定義命名,大寫字母開頭,不能和python關鍵字衝突。 -
類的程式碼體要放在縮進里。 -
屬性名自定義,不能和python關鍵字衝突。屬性值直接用等號賦值給自定義屬性名即可 -
實例方法名自定義,不能和python關鍵字衝突。方法(也就是函數)通過 def
關鍵字定義,和函數的定義語句很類似, -
實例方法的第一個參數必須傳 self
,固定值。(下詳) -
類中創建的屬性和方法可以被其所有的實例調用 -
實例的數目在理論上是無限的。我們可以同時「新建」多個實例【類被稱為「實例工廠」的由來】
示例程式碼
# 創建一個男朋友類對象
class MyBoyfriend:
sex = 'male'
def caring(self):
print('好了,不哭了~')
boyfriend = MyBoyfriend() # 調用類對象,得到男朋友實例對象。
print(type(MyBoyfriend)) # <class 'type'>
print(boyfriend) # <__main__.MyBoyfriend object at 0x109922400> MyBoyfriend類的是一個實例對象。後面的一串字元(0x109922400)表示這個對象的記憶體地址。
print(type(boyfriend)) # <class '__main__.MyBoyfriend'> 表示boyfriend類屬於MyBoyfriend類。
屬性(attribute)
在類中賦值的變數叫做這個類的「屬性」
方法(method)
在類中定義的函數叫做這個類的「方法」。
類中帶self
的參數的方法是實例方法,是類方法的一種形式,也是最常用的一種方法。
類的實例化
還是以上邊創建男朋友類的程式碼為例,類的實例化過程就是MyBoyfriend()
這句。 最後創建的實例對象被賦值給變數boyfriend
。
調用類對象的過程叫作類的實例化,即用某個類創建一個實例對象。
實現形式/偽程式碼是
實例變數名 = 類名() # 類名後+小括弧調用
實例對象調用類屬性和方法
實例化類對象後,得到一個實例對象。因為類的方法和屬性,實例對象都有。所以實例對象boyfriend
可以調用類中的屬性sex
和方法caring
。
調用類的屬性
實例名.屬性
調用類的方法
實例名.方法(傳參) # 參數不用考慮self
示例程式碼
# 調用類的屬性
print(boyfriend.sex) # 列印"male"
# 調用類的方法
boyfriend.caring() # 列印「好了,不哭了~」
值得說明的是,調用類方法時,傳參不用考慮self參數的存在。 fa
特殊參數:self
可以看到上例,為什麼實例調用方法時不用傳參,在定義時那個參數self又是什麼意思呢?
「
這個參數self的特殊之處:「在定義時不能丟,在調用時要忽略。」
」
1、代指實例化對象的作用
其實這個self
有點像JS中構造函數內部的this
。self是所有實例化對象的替身。指的是任何用這個類生成的實例化對象:
# self
class SelfMean:
content = '類SelfMean中的屬性'
def oneFn(self):
print(self.content) # self在此指向實例化的對象"selfMean"
selfMean = SelfMean()
selfMean.oneFn() # 列印類中content的值 - "類SelfMean中的屬性"
注意上例中,在oneFn函數內部,使用類中的屬性content時,不能直接當變數使用,否則如下寫法就會報錯:
class SelfMean:
content = '類SelfMean中的屬性'
def oneFn(self):
print(content) # 不用self調用類屬性,就會報錯NameError: name 'content' is not defined
selfMean = SelfMean()
selfMean.oneFn()
變數未經定義就使用,就會報這種NameError
的問題。詳見第三章錯誤類型總結篇。
有人會疑問,程式碼中oneFn上邊不是定義了content還給她賦值了嗎?怎麼能說未定義呢?
函數的作用域中也說了,自己oneFn函數內部找不到的變數,會向上找父級作用域、層層向上查詢乃至全局作用域的變數啊,他自己雖然沒有、但是他爸爸有啊!為啥還說未定義呢?
同志啊,醒醒。這裡是類啊!不是函數。類內部的變數是定義給實例化對象的屬性的啊。換句話說,上述程式碼實例化對象轉換成字典的模樣長下邊這樣:
selfMean = {
'content': '類SelfMean中的屬性',
'oneFn': 一個函數體在這裡~
}
所以你如果不用字典的方式(1、selfMean.content;2、selfMean[‘content’])調用這個屬性,他是拿不出來的。
而在類內部,實例化對象還沒出生,你不知道他未來的名字叫selfMean1、還是叫selfMean37。也就沒有辦法用selfMean.content或selfMean37[‘content’]的方式去調用它。
所以在這之前,需要有一個統一的self
,來代指未來的實例化對象,並達到提前在類內部
使用實例化對象的屬性和方法的目的。
使用方式就是self.屬性
、self.方法名
(除self以外的傳參)。看下例:
class SelfMean:
content = '類SelfMean中的屬性'
def oneFn(self):
print(self.content) # 提前在類中使用了實例化對象的屬性 content,等價於selfMean.content
self.twoFn('哈哈哈哈哈~') # 提前在類中使用了實例化對象的方法 twoFn、並傳參
def twoFn(self, txt):
print('實例化對象的第二個方法,列印內容:', txt)
selfMean = SelfMean()
selfMean.oneFn()
2、定義方法必傳self
看上邊的程式碼中,oneFn和twoFn都有self
參數,並且都是第一個參數,這並不是巧合。
只要在類中定義的方法,第一個參數就必須是self
。不過調用方法時,可以忽略它,不用給self傳參。
3、調用方法傳參時self可忽略
我們調用實例方法的時候,傳參不用考慮self
。以此往後類推就行:
class SelfParams:
content = '類SelfParams中的屬性'
def twoFn(self,name,sex,age,weight):
print(self.content)
print(name,sex,age,weight) # 2、依次列印傳遞過來的位置參數的值:小石頭 female 19 91
selfParams = SelfParams()
selfParams.twoFn('小石頭','female',19,91) # 1、調用方法時忽略self參數,依次按位置傳遞name,sex,age,weight的參數
初始化方法(構造函數)
1、定義初始化方法
定義初始化方法的格式是def __init__(self)
,是由init
關鍵字加左右兩邊的【雙】下劃線(__)組成。
雙下劃線是英文輸入法下,shift + 0右邊的那個鍵打出來的。
2、初始化方法的作用
無需調用自執行
一、當每個實例對象創建時(即類調用時),該方法內的程式碼無須調用就會自動運行。無需”實例名.init“的方式調用
# init
class InitTest:
def __init__(self):
a = 321
b = 345
print("初始化就會執行init里的程式碼: ", a+b)
initTest = InitTest()
# 運行後直接列印:初始化就會執行init里的程式碼: 666
可見 ,觸發類對象的調用時,就直接觸發了__init__方法的調用。
為類屬性設置初始值
一般情況下,我們都會在初始化方法內部完成類屬性的創建,為類屬性設置初始值,這樣類中的其他方法就能直接、隨時調用。
上述程式碼改寫如下:
class InitTest:
def __init__(self):
# 為類屬性設置初始值,要寫在__init__函數內部的最上方,否則會報錯
self.a = 321 # 注意self的存在。
self.b = 345 # 同上
# 定義完屬性的初始值以後,才能在下變寫__init__裡邊要處理的其他邏輯
print("初始化就會執行__init__里的程式碼")
self.plusAd()
def plusAd(self):
print(self.a + self.b) # 使用類屬性
initTest = InitTest()
# 列印結果如下
# 初始化就會執行init里的程式碼
# 666
3、初始化方法接收其他參數
除了設置固定常量,初始化方法同樣可以接收其他參數,並把這些參數賦值給類的屬性並被類中其他方法使用:
# 初始化方法接收參數
class InitParams:
def __init__(self,aP,bP):
self.a = aP
self.b = bP
print('初始化執行並設置屬性、把參數aP和bP的值給了屬性a和b')
self.plusAd()
def plusAd(self):
print(self.a + self.b) # 其他方法也能用同一個屬性
def sub(self):
print(self.a - self.b) # 其他方法也能用公用的屬性
initParams1 = InitParams(12, 4) # __init__需要的參數在類調用時傳遞
# 列印結果:
# 初始化執行並設置屬性、把參數aP和bP的值給了屬性a和b
# 16
# 8
initParams2 = InitParams(30, 5) # __init__需要的參數在類調用時傳遞,可以多次調用,傳不同的參數,進而得到不一樣的結果
# 列印結果:
# 初始化執行並設置屬性、把參數aP和bP的值給了屬性a和b
# 35
# 25
當初始化方法__init__有多個參數的時候,在實例化(類調用)的時候就要傳入相應的值。
上例第一次調用,12傳給了aP、4傳給了bP。
上例第二次調用,30傳給了aP、5傳給了bP。
這也是初始化方法的好處,傳參一次可被多次調用,
番外 – 面向對象
面向過程
面向過程編程:首先分析出解決問題所需要的步驟(即「第一步做什麼,第二步做什麼,第三步做什麼」),然後用函數實現各個步驟,再依次調用。一個示例:
# 全局變數定義
globalA = 12
globalB = 20
globalCount = 0
# 加
def plus():
global globalCount
globalCount = globalA + globalB
sub()
# 減
def sub():
global globalCount
globalCount = globalCount - globalA/2
print(globalCount)
# 主流程
def main():
plus()
# 啟動流程
main()
我們根據「全局變數數據整理——主流程——加法計算——減法計算」這個過程封裝了三個函數,再依次調用,按規定順序執行程式。
面向對象
面向對象編程,會將程式看作是一組對象的集合。通過調用對象的屬性和方法,來拼湊完成一段功能。
# 面向對象
class Calculator:
def __init__(self,a,b):
# 公共屬性定義
self.globalA = a
self.globalB = b
self.globalCount = 0
# 是加法方法,能做加法
def plus(self):
self.globalCount = self.globalA + self.globalB
print(self.globalCount)
# 是減法方法,能做減法
def sub(self):
self.globalCount = self.globalCount - self.globalA/2
print(self.globalCount)
calculator = Calculator(12,20)
calculator.plus()
calculator.sub()
用這種思維設計程式碼時,考慮的不是程式具體的執行過程(即先做什麼後做什麼),而是考慮先創建某個類,在類中設定好屬性和方法,即是什麼,和能做什麼。
接著,再以類為模版創建一個實例對象,用這個實例去調用類中定義好的屬性和方法(plus、sub)即可。
面向對象的好處
-
參數的傳遞會比普通函數要省事很多。(不必考慮全局變數和局部變數,因為類中的方法可以直接調用屬性。) -
程式碼的可復用性也更高。(類能封裝更多的東西,既能包含操作數據的方法,又能包含數據本身。) -
程式碼結構會更清晰。(程式碼的可讀性、可拓展性和可維護性這幾個方面都會優於面向過程編程。) -
一目了然。(面向對象編程將程式碼具體的數據和處理方法都封裝在類中,不用完全了解過程也可以調用類中的各種方法。並且還可以分開調用) -
可以在 Python 中輕鬆地調用各種標準庫、第三方庫和自定義模組(別人寫好的類框架程式碼)
本文使用 mdnice 排版