[python] [VI coding] 第十五章 類別與物件 - 教學區

[python] [VI coding] 第十五章 類別與物件

文章瀏覽次數 1362

特種兵

特種兵圖像(預設)

2021-09-24 17:28:19

From:211.23.21.202

第十五章 類別與物件

之前我們使用函數來組織程式,利用內建的資料類型來管理數據。

過往都是利用 python 原生定義好的類別來撰寫程式,除了能自己定義函數、變數外,還可以自訂類別。

接下來這幾章,就來把物件導向、自訂類別的概念分享給大家。

15-1 什麼是類別

類別也是一種資料型態的體現,其實之前已經使用過別人寫好的類別。

當 import 函式庫進來,其實就是載入類別或物件。以 math 模組為例,載入 math 後,使用 math.pi 得到 pi 定義的數值,這個 pi 就是屬於 math 的一個屬性,屬性可以先當成變數來理解。

而 math.sin() 可以取得 sin 的值,這個 sin() 是定義在 math 物件的函數,也就是 math 物件裡的一個方法。

所以在使用 python 函式庫,就是在使用類別或物件,而現在我們打算自訂類別。

15-2 自訂類別工廠

我們來撰寫一個銀行類別,裡面有存款,並且能執行存款、提款等動作。

如果按照之前的做法,大概會利用原生的資料型態與自訂的函數完成這個任務。

現在,要來嘗試定義一個新類別,使用類別的方式來處理資料:

>>> class Bank:
... 	'''這是一個銀行類別,裡面有存款金額與存款、提款等功能'''
>>>

class 是保留字,用來定義類別,而類別的名稱第一個字習慣上是使用大寫字母。

範例的類別裡面只放了註解,用來說明這個類別的用途。這也稱為 docstring 文件字串。

在這個類別當中,可定義想要的變數與函數等。外觀形式上就是在原本的函數與變數上再加一層類別的概念。

定義一個類別,就是建立了一個新類別工廠。

>>> Bank
<class '__main__.bank'>

因為將 Bank 定義在最頂層,之前提過,其實最外面有個 __main__ 的存在,因此這個類別物件的全名是 __main__.Bank

定義類別,就像是定義一個產生這個類別物件的工廠,能夠建立無限個類別物件,亦即利用無限個變數初始化字典、列表等,而每個變數都是獨立的,但又具有該類別的共同特性。

已經定義類別後,現在來產生類別物件:

>>> mybank = Bank()
>>> mybank
<__main__.Bank object at 0x01F74430>

從反回值看出,mybank 是屬於 Bank 類別的物件,並且顯示了記憶體位置。

建立新的物件稱為實例化,新物件就稱為這個類別的實例。在 python 中,每一個物件都是某個類別的實例,所以能產生無限個類別物件,就像可產生無限個變數一樣,今天可以有 mybank ,也可以是 yourbank 等等。

15-3 類別屬性

屬性與變數極為類似,之前在函數中定義變數,一般情況下該變數只存在於該函數中。

函數結束,變數也隨之消失,在類別裡定義的屬性或方法也是一樣的道理。

現在賦值給 mybank 這個 Bank 類別實例:

>>> mybank.money = 100
>>> mybank.rate = 1.0

這個語法應該很熟悉,就像是使用某個模組裡的變數,例如 import math ,然後調用 math.pi 這個值。

在這裡,為物件內的元素命名並賦值,這些元素就稱為類別物件中的屬性。

上例解釋為:變數 mybank 指向了一個 Bank 類別,這個類別有兩個屬性,一個指向整數,另一個則是浮點數。可使用相同語法取得屬性的值:

>>> mybank.money
100
>>> rate = mybank.rate
>>> rate
1.0

在 rate 的部分,把 mybank 裡的屬性 rate 的值賦值給一般的變數 rate ,這沒有衝突,

因為 rate 在 __main__ 當中,而 mybank 的 rate 是在 __main__ 裡的 mybank 類別中。

類別的屬性可以像一般變數一樣,在任何式子中被調用:

>>> f'my money is {mybank.money} and now rate is {mybank.rate}'
'my money is 100 and now rate is 1.0'
>>> 'my money is %d and now rate is %g' % (mybank.money, mybank.rate)
'my money is 100 and now rate is 1'
>>> new_money = math.sqrt(mybank.money)
>>> new_money
10.0

也可以把整個實例當作函數參數來傳遞:

def printMoney(b):
	print(f'目前有 {b.money} 元')

printMoney(mybank)

printMoney 是一般的函數,可以把整個實例當作函數參數來傳遞,當然參數若是一般變數,也可以把屬性當作參數來傳遞。

在 printMoney 函數中,b 是 mybank 的別名,所以在函數內改變變數的值,也會影響到物件的屬性值。

類別的屬性也可以定義在類別當中,這部分之後再說。

15-4 財富類別

類別需要哪些屬性,有時是很明確的,但也有可能需要經過一番思考,現在建立一個財富的類別。

class Riches:
	''' 這是一個財富類別
	裡面有兩個屬性,分別是 money 現金與 mybank 銀行存款'''

從這個類別的說明中,知道現金為整數型態,銀行存款則是 Bank 類別的物件。

所以要實例化出財富的類別物件,並且賦值給它的屬性:

myriches = Riches()
myriches.money = 1000
myriches.mybank = Bank()
myriches.mybank.money = 100
myriches.mybank.rate = 1.0

上面的式子中:

第4行,myriches.mybank.money = 100 的意思是,前往 myriches 指向的物件,找到 mybank 屬性,接著前往 mybank 屬性所指向的物件找到 money 屬性,最後賦值給它。等於是有兩層類別的概念。

第5行,也是一樣的道理。

15-5 反回實例

一個實例也可以作為函數的反回值,這裡定義一個函數,riches 類別物件是它的參數。

下面例子中,因為這個人的現金就是他太太的現金,所以最後反回一個他太太的財富物件。

def getMywifeRiches(myriches):
	mywiferiches = Riches()
	mywiferiches.money = myriches.money
	return mywiferiches	

呼叫剛剛的函數,將太太的錢回傳後,印出來看看。

>>> mwr = getMywifeRiches(myriches)
>>> printMoney(mwr)
目前有 100 元

15-6 物件的值是可變的

我們可通過對物件屬性賦值來改變屬性的值,就像操作變數一樣。

下面的例子中,這個月發薪水了,所以累加銀行存款。

myriches = Riches()
myriches.money = 1000
myriches.mybank = Bank()
myriches.mybank.money = 100
myriches.mybank.money += 30000

也可以考慮寫一個函數來增加財富:

def addRiches(myriches, m, n):
	myriches.money += m # 累加現金
	myriches.mybank.money += n # 累加存款

myriches = Riches()
myriches.money = 1000
myriches.mybank = Bank()
myriches.mybank.money = 100
myriches.mybank.rate = 1.0
addRiches(myriches, 1000, 2000)
printMoney(myriches)
# 現金 2000
printMoney(myriches.mybank)
# 存款 2100

15-7 複製

別名會降低程式的可讀性,因利用別名可改變物件屬性的值,在除錯上是困難且不好追蹤的,所以採複製物件的方式來取代別名。

>>> mybank = Bank()
>>> mybank.money = 100
>>> mybank.rate = 1.0
>>> import copy
>>> yourbank = copy.copy(mybank)

mybank 和 yourbank 的數據相同,但它們是兩個不同的 Bank 物件。

>>> mybank.money
100
>>> yourbank.money
100
>>> mybank is yourbank
False
>>> mybank == yourbank
False

正如我們所說的,is 不相等是意料中事,== 運算子的結果可能令人意外。

雖然它們的值是相同的,但實際上 == 運作的方式是比較物件的標誌 (identity) 。

另外,copy.copy() 的副作用也很明顯:

>>> myriches = Riches()
>>> myriches.money = 100
>>> myriches.mybank = Bank()
>>> myriches.mybank.money = 1000
>>> yourriches = copy.copy(myriches)
>>> yourriches is myriches
False
>>> yourriches.mybank is myriches.mybank
True
>>> yourriches.mybank.money is myriches.mybank.money
True

相信多數人不希望是這樣的結果,它的問題是容易造成錯誤的更改。

>>> myriches.mybank.money -= 50
>>> myriches.mybank.money
950
>>> yourriches.mybank.money
950

改變了 myriches.mybank.money 的值,結果 yourriches.mybank.money 的值也被影響到了,這很令人困擾。

幸好還有一種稱為深層複製的方式 deep copy 可以使用,它會複製所有包含在內的類別物件。

>>> hisriches = copy.deepcopy(myriches)
>>> myriches is hisriches
False
>>> myriches.mybank is hisriches.mybank
False

這樣看起來它們就互不相干了。下一章再接著探討類別方法,本章先著重在屬性。

除錯

開始學習物件時,會遇到關於物件的錯誤訊息,像是一個不存在的屬性會引發 AttributeError 的錯誤

>>> myriches.money
100
>>> myriches.gold
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Riches' object has no attribute 'gold'

我們可以使用 type() 來確認物件的類型:

>>> type(myriches)
<class '__main__.Riches'>

也可以使用 isinstance() 來確認物件是不是某個類別的實例:

>>> isinstance(myriches, Riches)
True

使用 hasattr() 還可以確認這個物件有沒有某個屬性:

>>> hasattr(myriches, 'money')
True
>>> hasattr(myriches, 'gold')
False

第一個參數是任何物件,第二個參數是一個屬性名稱的字串。

最後,當然也可以利用 try-exception 來檢查物件中有沒有我們要的屬性:

try:
	money = myriches.money
except AttributeError:
	money = 0
print(money)

當第2行發生 AttributeError ,即屬性不存在時,會自動將 money 的值設定為 0,

這樣就可以編寫出適應多變結構的程式,並且在意外或錯誤發生時,不至於讓程式停擺。

動動腦

  1. 該如何將屬性直接定義在類別中。
class Riches:
	money = 100

myriches = Riches()
myriches.money += 100
print(myriches.money)
# 200

習題

練習 1 檔名: 15-1.py

先宣告一個 Bank() 類別,裡面有兩個屬性,一個是 money 代表存款,一個是 rate 代表利率。

實例化出兩個類別物件,一個是 John ,另一個是 May ,這兩個人是夫妻。他們各自有自己的存款,不同的銀行利率。

寫一個通用函數 printMoney() ,傳入一個 Bank 類別物件參數,呼叫兩次來印出他們婚前的存款。

接著呼叫一個函數 getMarry() 代表他們結婚,婚後約定 John 的存款就是 May 的存款,但 May 的存款還是 May 的存款。

所以 getMarry 傳入兩個物件,分別修改這兩個人婚後的財產變化。

再次呼叫兩次 printMoney() ,來顯示雙方婚後的存款。

後來,John 因為身上沒錢也沒存款了,向地下錢莊借錢,於是執行 lend() 函數表示 John 借了一筆錢。

地下錢莊來要債,他們說 John 要還的錢是原本銀行利率的 10 倍,所以執行 giveMoney() 函數,讓 May 幫他還錢。

事情終於結束,執行 totalMoney() 函數帶入兩個人的類別物件,並回傳他們最後剩下的財產是多少。

參考資料

python 的屬性

影片

第十五章 類別與物件

最後更新:2021-10-08 20:39:07

From: 111.249.165.250

By: 特種兵