Python 入門指南 5.0
單元 13 - 資料類別、 __init__() 方法與封裝
Python 中有一些特別的方法 (method) ,這些方法的識別字 (identifier) 前後都用兩個底線圍起來,最基本的是以下這兩個
__del__()
每一種都有特定的功能,其中的 __init__() 方法就是物件 (object) 建立時所執行的方法,又稱為建構子 (constructor) ,舉例如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | # 定義具有建構子的類別
class ClassDemo01:
# 定義建構子
def __init__(self, a, b): # 需要兩個參數
# 依參數設定實體屬性
self.a = a
self.b = b
# 設定實體方法
def do_something(self):
return self.a + self.b
# 以下是執行部分
# 建立 ClassDemo01 型態的物件變數 d
d = ClassDemo01(14, 25)
# 印出 d 屬性 a 與 b 的相加值
print(d.do_something())
# 檔名: class_demo06.py
# 說明:《Python入門指南》的範例程式
# 網站: http://kaiching.org
# 作者: 張凱慶
# 時間: 2023 年 5 月
|
這裡把原本 Demo 類別 (class) 的 set_att() 改成 __init__()
4 6 7 | def __init__(self, a, b): # 需要兩個參數
self.a = a
self.b = b
|
之所以把 __init__() 稱為建構子,這是因為 __init__() 是在實體 (instance) 物件建立時所執行的方法,通常用來設定實體屬性 (instance attribute) 。
do_something() 方法是一樣的,至於建立 Demo 型態 (type) 的變數 (variable) d 時就像呼叫 set_att() ,可以直接提供參數 (parameter) 設定屬性 (attribute) a 及 b ,然後印出 do_something() 方法的回傳值 (return value)
15 17 | d = ClassDemo01(14, 25)
print(d.do_something())
|
執行結果如下
$ python class_demo06.py |
39 |
$ |
但是屬性如果連結到可變的 (mutable) 資料型態 (data type) ,這時候我們需定義解構子 (destructor) 來刪除這些屬性,解構子就是 __del__() 方法,舉例如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | # 定義具有建構子的類別
class ClassDemo02:
# 定義建構子
def __init__(self, *p):
# 將參數依序放入 data 內
self.data = []
for i in p:
self.data.append(i)
# 定義解構子
def __del__(self):
# 刪除屬性
del self.data
# 印出刪除訊息
print("所有資料已刪除")
# 定義計算 data 總和
def do_something(self):
# 暫存結果的變數
result = 0
# 計算總和
for i in self.data:
result += i
# 回傳暫存結果的變數
return result
# 以下是執行部分
# 建立 ClassDemo02 型態的物件變數 d
d = ClassDemo02(1, 2, 3, 4, 5)
# 印出 data 總和
print(d.do_something())
# 檔名: class_demo05.py
# 說明:《Python入門指南》的範例程式
# 網站: http://kaiching.org
# 作者: 張凱慶
# 時間: 2023 年 5 月
|
這裡 ClassDemo02 的建構子會依參數設定屬性 data , data 是一個串列 (list) ,屬於可變的資料型態
4 6 7 8 | def __init__(self, *p):
self.data = []
for i in p:
self.data.append(i)
|
解構子會用關鍵字 del 刪除 data ,所後印出相關提示訊息
11 13 15 | def __del__(self):
del self.data
print("所有資料已刪除")
|
由於每個可變資料型態都是個別的物件,當作用域 (scope) 屬於特定類別時,這是說這個可變資料型態物件只跟這個特定類別產生的實體物件連結,當原本特定類別產生的實體物件不再使用的時候, Python 的資源回收機制 (garbage collection) 會回收特定類別實體物件所使用的記憶體,可是額外連結到的可變資料型態物件卻不一定會被回收,保險起見是自己寫解構子刪除,好讓直譯器 (interpreter) 可以完整釋放記憶體空間。
do_something() 方法一樣會計算所有參數的和,底下執行部分是先建立 ClassDemo02 的物件,然後印出 do_something() 方法的回傳值
29 31 | d = ClassDemo02(1, 2, 3, 4, 5)
print(d.do_something())
|
執行結果如下
$ python class_demo07.py |
15 |
所有資料已刪除 |
$ |
如果要保護資料就得將屬性封裝 (encapsulation) , Python 封裝的寫法是將屬性之前加上連續兩條底線,舉例如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | # 定義具有建構子的類別
class ClassDemo03:
# 定義建構子
def __init__(self, data):
# 封裝屬性
self.__data = data
# __data 的 getter
@property
def data(self):
print("資料已回傳")
return self.__data
# doubledata 的 getter
@property
def doubledata(self):
print("雙倍資料已回傳")
return self.__data * 2
# __data 的 setter
@data.setter
def data(self, new_data):
self.__data = new_data
print("資料已設定")
# __data 的 deleter
@data.deleter
def data(self):
del self.__data
print("資料已刪除")
# 以下是執行部分
# 建立 ClassDemo03 型態的物件變數 d
d = ClassDemo03(100)
# 重新設定已封裝的屬性 _data
d.data = 99
# 印出 data 屬性
print(d.data)
# 印出 doubledata 屬性
print(d.doubledata)
# 刪除 data 屬性
del d.data
# 檔名: class_demo08.py
# 說明:《Python入門指南》的範例程式
# 網站: http://kaiching.org
# 作者: 張凱慶
# 時間: 2023 年 5 月
|
建構子中的 __data 已被封裝,因此外界不能直接用小數點存取 __data
4 6 | def __init__(self, data):
self.__data = data
|
如果我們還是有需要在外部程式設定以封裝的屬性的話該怎麼辦呢?這就要替屬性設定的 setter 與 getter 方法了, Python 預設是以 @property 設定 getter
9 10 11 12 | @property
def data(self):
print("資料已回傳")
return self.__data
|
這樣就可以用小數點取得 data 屬性, data 會回傳已封裝的 __data
38 | print(d.data)
|
所以外界可取得的屬性名稱就變成 data ,在類別中實際是以 __data 運作。
如果有其他屬性需要經過計算設定,也可以用這種方式定義其他屬性,如第 22 行增加了定義 doubledata 屬性
15 16 17 18 | @property
def doubledata(self):
print("雙倍資料已回傳")
return self.__data * 2
|
如果已封裝屬性還是需要在外界設定,這就需要定義 setter
21 22 23 24 | @data.setter
def data(self, new_data):
self.__data = new_data
print("資料已設定")
|
其實 setter 是真的有需要,不得已的方式,我們在「軟體開發篇」介紹的 Encrypt 類別會把密碼表屬性封裝,但是不會設定 getter 或 setter ,僅會設定 __str__() 來呈現 Encrypt 型態物件的字串 (string) 形式。
這樣底下執行部分就可以直接用指派運算子 (assignment operator) 設定 data 屬性
38 | d.data = 99
|
如果要在外界使用關鍵字 del 刪除屬性,這需要設定 deleter
27 28 29 30 | @data.deleter
def data(self):
del self.__data
print("資料已刪除")
|
這樣在底下執行部分就可以用 del 刪除 data 屬性,進入 deleter 就會刪除封裝的 __data
42 | del d.data
|
注意 deleter 也是有需要才要定義,不然定義建構子就夠了。
執行結果如下
$ python class_demo08.py |
資料已設定 |
資料已回傳 |
99 |
雙倍資料已回傳 |
198 |
資料已刪除 |
$ |
Python 中有個特殊的資料類別 (data class) ,僅作為定義資料用,必且需要從標準程式庫 (standard library) 引入 dataclasses ,舉例如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | # 從標準程式庫引入 dataclass
from dataclasses import dataclass
# 定義資料類別
@dataclass
class MemberData:
# 宣告屬性及型態
name: str
id: int
psd: str
# 以下是執行部分
# 建立 MemberData 型態的變數 d
d = MemberData("Default", 0, "1234")
# 印出變數 d 中的資料
print(d.name)
print(d.id)
print(d.psd)
# 檔名: class_demo05.py
# 說明:《Python入門指南》的範例程式
# 網站: http://kaiching.org
# 作者: 張凱慶
# 時間: 2023 年 5 月
|
首先,第 2 行利用 from 及 import 兩個關鍵字 (keyword) ,從 dataclasses 引入 dataclass
2 | from dataclasses import dataclass
|
有關 form 及 import 的使用方式在單元 18 - 模組與套件才會詳細説明。
定義資料類別需要在關鍵字 class 上方加入裝飾子 (decorator) @dataclass
5 6 8 9 10 | @dataclass
class MemberData:
name: str
id: int
psd: str
|
MemberData 類別很簡單,只有定義三個類別屬性 (class attribute) ,注意這裡類別屬性後面接冒號,冒號後面是型態的識別字,這是 Python 型態標記 (type annotation) 的方式,也就是只標記屬性的型態。
有關型態標記在單元 17 - 型態標記與檢查資料型態會詳細説明。
底下執行部分可以看到如何使用資料類別,也就是直接以類別建構子代入設定屬性
14 16 17 18 | d = MemberData("Default", 0, "1234")
print(d.name)
print(d.id)
print(d.psd)
|
執行結果如下
$ python class_demo05.py |
Default |
0 |
1234 |
資料已刪除 |
$ |
其實資料類別只是處理資料的一種語法糖,可以直接對應資料庫來處理資料,接下來我們繼續討論 Python 的資料模型 (data model) ,包括特別屬性跟迭代器 (iterator) 。
中英文術語對照 | |
---|---|
屬性 | attribute |
類別 | class |
類別屬性 | class attribute |
建構子 | constructor |
資料類別 | data class |
資料模型 | data model |
資料型態 | data type |
裝飾子 | decorator |
解構子 | destructor |
封裝 | encapsulation |
資源回收機制 | garbage collection |
識別字 | identifier |
實體 | instance |
實體屬性 | instance attribute |
迭代器 | iterator |
直譯器 | interpreter |
關鍵字 | keyword |
方法 | method |
可變的 | mutable |
物件 | object |
參數 | parameter |
回傳值 | return value |
作用域 | scope |
標準程式庫 | standard library |
字串 | string |
型態 | type |
型態標記 | type annotation |
變數 | variable |
重點整理 |
---|
1. __init__() 為類別中的建構子,主要用來建立實體屬性。 |
2. __del__() 為類別中的解構子,主要用來刪除可變的實體屬性。 |
3. 封裝是在實體屬性的識別字之前加上連續兩條底線。 |
4. @property 用來標示實體屬性的 getter , 實體屬性的 setter 是要用 @ 加上別識字與小數點 . ,然後是 setter ,至於 deleter 則是加上 deleter 。 |
5. 定義資料類別要從標準程式庫的 dataclasses 引入 dataclass ,然後在類別定義前加上 @dataclass 。 |
問題與討論 |
---|
1. 為什麼要定義建構子?定義建構子有什麼方便的地方? |
2. 什麼時候需要定義解構子? |
3. 什麼是封裝?為什麼要對屬性做封裝? |
4. 什麼情況用資料類別會比較方便? |
練習 |
---|
1. 承接上一單元的練習 7 ,將新程式寫在 exercise1301.py 中,把 set_value() 改成用 __init__() 設計。 參考程式碼 |
2. 承上題,將新程式寫在 exercise1302.py 中,繼續對屬性進行封裝。 參考程式碼 |
3. 承上題,將新程式寫在 exercise1303.py 中,繼續把 add() 改成 addition() ,並在上一行加入 @property ,其餘減法、乘法等類推。 參考程式碼 |
4. 承上題,將新程式寫在 exercise1304.py 中,繼續把 __init__() 的內容移回 set_value() 中,並在 __init__() 呼叫 set_value() 建立屬性,然後在執行部分在使用者輸入結束後用 set_value() 重新設定屬性,並印出相對應的提示訊息。 參考程式碼 |
5. 承接上一單元的練習 11 ,將新程式寫在 exercise1305.py 中,把 set_value() 改成用 __init__() 設計。 參考程式碼 |
6. 承上題,將新程式寫在 exercise1306.py 中,繼續對屬性進行封裝。 參考程式碼 |
7. 承上題,將新程式寫在 exercise1307.py 中,繼續在 factorial() 的上一行加入 @property ,其餘 fibonacci() 類推。 參考程式碼 |
8. 承上題,將新程式寫在 exercise1308.py 中,繼續把 __init__() 的內容移回 set_value() 中,並在 __init__() 呼叫 set_value() 建立屬性,然後在執行部分在使用者輸入結束後用 set_value() 重新設定屬性,並印出相對應的提示訊息。 參考程式碼 |
9. 寫一個程式 exercise1209.py , 設計一個資料類別 Student ,然後用串列建立數個 Student ,並在最後印出資料內容。 參考程式碼 |
10. 承上題,將新程式寫在 exercise1310.py 中,將 Student 改成 Product ,並以資料串列記錄銷售,最後計算總銷售金額。 參考程式碼 |