Python 入門指南 5.0

單元 13 - 資料類別、 __init__() 方法與封裝

~~學習進度表~~

Python 中有一些特別的方法 (method) ,這些方法的識別字 (identifier) 前後都用兩個底線圍起來,最基本的是以下這兩個

__init__()
__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) ab ,然後印出 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 的建構子會依參數設定屬性 datadata 是一個串列 (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

如果我們還是有需要在外部程式設定以封裝的屬性的話該怎麼辦呢?這就要替屬性設定的 settergetter 方法了, 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 類別會把密碼表屬性封裝,但是不會設定 gettersetter ,僅會設定 __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 行利用 fromimport 兩個關鍵字 (keyword) ,從 dataclasses 引入 dataclass

 2
from dataclasses import dataclass

有關 formimport 的使用方式在單元 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 ,並以資料串列記錄銷售,最後計算總銷售金額。 參考程式碼

上一頁 單元 12 - 類別
回 Python 入門指南 5.0 首頁
下一頁 單元 14 - 資料模型、特別屬性與迭代器
回 Python 教材首頁
回程式語言教材首頁