Python 入門指南 5.0

單元 14 - 資料模型、特別屬性與迭代器

~~學習進度表~~

Python 內建很多特殊的屬性 (attribute) 或方法 (method) ,設定專門的屬性或方法可以讓類別 (class) 具有通用或特定功能,這些屬性或方法在識別字 (identifier) 的前後用連續兩條底線圍起來

__module__, __name__, __dict__
__str__, __repr__, __format__
__bool__
__iter__, __next__

我們已經見過兩種通用功能的特殊方法,也就是建構子 (constructor) 與解構子 (destructor) , Python 利用通用的方法及屬性讓每個實際設計物件 (object) 能依照相同的資料模型 (data model) 進行設計,這樣的好處是物件之間減少差異化,也比較好設定特定功能的物件。

這裡先看到 __module____name____dict____module__ 回傳所在的作用域 (scope) , __name__ 回傳所在的函數 (function) 或類別識別字, __dict__ 則是回傳內建名稱的配對型態,舉例如下

 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
# 定義具有建構子的類別
class ClassDemo04:
    # 印出內建屬性 __module__
    def print_module(self):
        print(self.__module__)

    # 印出內建屬性 __name__
    def print_name(self):
        print(ClassDemo04.__name__)

    # 印出內建屬性 __dict__
    def print_dict(self):
        print(ClassDemo04.__dict__)

# 以下是執行部分
# 建立 ClassDemo04 型態的物件變數 d
d = ClassDemo04()
# 呼叫 print_module()
d.print_module()
# 呼叫 print_name()
d.print_name()
# 呼叫 print_dict()
d.print_dict()

# 檔名: class_demo09.py
# 說明:《Python入門指南》的範例程式
# 網站: http://kaiching.org
# 作者: 張凱慶
# 時間: 2023 年 5 月

ClassDemo04 有三個方法, print_module() 印出物件的 __module__ 屬性, print_name() 印出 __name__ ,而 pint_dict() 印出 __dict__

 4
 5
 6
 8
 9
10
12
13
    def print_module(self):
        print(self.__module__)

    def print_name(self):
        print(ClassDemo04.__name__)

    def print_dict(self):
        print(ClassDemo04.__dict__)

執行部分就是依次呼叫三個方法,結果如下

$ python class_demo09.py
__main__
ClassDemo04
{'__module__': '__main__', 'print_module': <function ClassDemo04.print_module at 0x10299c7c0>, 'print_name': <function ClassDemo04.print_name at 0x10299cae0>, 'print_dict': <function ClassDemo04.print_dict at 0x10299d760>, '__dict__': <attribute '__dict__' of 'ClassDemo04' objects>, '__weakref__': <attribute '__weakref__' of 'ClassDemo04' objects>, '__doc__': None}
$

印出的第一行為 __main__ ,表示現在的作用域為 __main__ ,也就是直接執行的作用域

__main__

第二行為 ClassDemo04 ,這是類別名稱

ClassDemo04

第三行為 ClassDemo04 中有的名稱,像是 __module__ 屬性

'__module__': '__main__'

繼續看到 print_module() 方法,裡頭 value 中還提供 print_module() 在記憶體中的起始位置

'print_module': <function ClassDemo04.print_module at 0x10299c7c0>

__dict__ 回傳的資料型態 (data type) 名稱為 mappingproxy ,這不是字典 (dictionary) ,我們知道是配對型的資料型態就夠了,之所以不是字典,主要原因是不讓使用者修改 __dict__ 屬性。

繼續看到 __str__()__repr__()__format__() ,這三個方法都是回傳物件的字串 (string) 形式,不過略有不同,舉例如下

 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
# 定義具有建構子的類別
class ClassDemo05:
    # 定義建構子
    def __init__(self, n):
        self.value = n

    # 定義字串形式
    def __str__(self):
        return str(self.value)

    # 定義在互動介面的字串形式
    def __repr__(self):
        return "<classdemo05: %s>" % str(self.value)

    # 定義格式化的字串形式
    def __format__(self, code):
        return "-- {0} --".format(str(self.value))

# 以下是執行部分
# 建立 ClassDemo05 型態的物件變數 d
d = ClassDemo05(1000)
# 印出變數 d
print(d)
# 印出格式化的變數 d
print(format(d))

# 檔名: class_demo10.py
# 說明:《Python入門指南》的範例程式
# 網站: http://kaiching.org
# 作者: 張凱慶
# 時間: 2023 年 5 月

__str__() 為物件最基本字串形式,由於 ClassDemo05 只有一個屬性 value ,因此 __str__() 設定成回傳 vlaue 的字串

 8
 9
    def __str__(self):
        return str(self.value)

如果直接把物件放到 print() 函數中,就會自動啟用 __str__() ,得到 __str__() 回傳結果

21
23
d = ClassDemo05(1000)
print(d)

__format__() 則是定義在內建函數 format() 的回傳結果,這是物件的格式化字串形式

16
17
    def __format__(self, code):
        return "-- {0} --".format(str(self.value))

所以底下執行部分是直接印出 format() 的回傳結果

25
print(format(d))

至於 __repr__() 是在 Python Shell 回傳的字串形式

12
13
    def __repr__(self):
        return "<classdemo05: %s>" % str(self.value)

接下來移到 IDLE 的 Python Shell 來執行,下圖可以看到執行結果

以上在印出變數 dformat(d) 之後,再輸入 d ,得到的結果就是 __repr__() 的回傳值。

繼續看到 __bool__()__bool__() 回傳物件的布林值 (Boolean value) ,舉例如下

 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
# 定義具有建構子的類別
class ClassDemo06:
    # 定義建構子
    def __init__(self, n):
        self.value = n

    # 定義字串形式
    def __str__(self):
        return str(self.value)

    # 定義布林性質
    def __bool__(self):
        # 判斷屬性 value 大於 0
        if self.value > 0:
            # 大於 0 物件為 True
            return True
        else:
            # 反之物件為 False
            return False

# 以下是執行部分
# 建立 ClassDemo06 型態的物件變數 d1
d1 = ClassDemo06(1000)
# 判斷變數 d1 的真假值
if d1:
    # d1 為 True 印出 d1
    print(d1)
# 建立 ClassDemo06 型態的物件變數 d1
d2 = ClassDemo06(-100)
# 判斷變數 d2 的真假值
if not d2:
    # d2 為 False 印出 相關訊息
    print("d2為False")

# 檔名: class_demo11.py
# 說明:《Python入門指南》的範例程式
# 網站: http://kaiching.org
# 作者: 張凱慶
# 時間: 2023 年 5 月

ClassDemo06 依靠屬性 value 來判斷真假值,如果 value 大於 0 就回傳 True ,反之回傳 False

12
14
16
17
19
    def __bool__(self):
        if self.value > 0:
            return True
        else:
            return False

由於 ClassDemo06 的物件有真假值,因此可以直接當條件,例如執行部分先後建立正值的 d1 以及負值的 d2 ,然後印出相對應的訊息

23
25
27
29
31
33
d1 = ClassDemo06(1000)
if d1:
    print(d1)
d2 = ClassDemo06(-100)
if not d2:
    print("d2為False")

執行結果如下

$ python class_demo11.py
1000
d2為False
$

繼續看到 __iter__()__next__() ,這兩個方法用來設定迭代器 (iterator) 物件,迭代器物件產生數列並且會耗盡的物件,舉例如下

 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
# 定義迭代器類別
class ClassDemo07:
    # 定義建構子
    def __init__(self, number):
        # 迭代器的最大數量
        self.number = number
        # 累計迭代器的數量
        self.count = 1

    #  迭代器初始化並且回傳迭代器本身
    def __iter__(self):
        # 迭代器初始化
        self.n = 1
        # 回傳迭代器本身
        return self

    # 回傳迭代器的下一個物件
    def __next__(self):
        # 計算下一個迭代器物件
        if self.count <= self.number:
            # 計算 10 的次方
            self.n = self.n * 10
            # 計數遞增以符合結束條件
            self.count += 1
            # 回傳 10 的次方值
            return self.n
        else:
            # 超出迭代器範圍
            raise StopIteration

# 以下是執行部分
# 建立 ClassDemo07 型態的物件變數 d
d = ClassDemo07(10)
# 依次印出迭代器物件中的次方值
for i in d:
    print(i)

# 檔名: class_demo12.py
# 說明:《Python入門指南》的範例程式
# 網站: http://kaiching.org
# 作者: 張凱慶
# 時間: 2023 年 5 月

迭代器必要兩個設定,首先是 __iter__()

11
13
15
    def __iter__(self):
        self.n = 1
        return self

__iter__() 將迭代器產生的數列初始化,這裡是將屬性 n 設定為整數 1 ,然回傳 self ,也就是迭代器物件本身。

接下來第二個必要設定是 __next__() ,這是計算數列產生的方式

18
20
22
24
26
27
29
    def __next__(self):
        if self.count <= self.number:
            self.n = self.n * 10
            self.count += 1
            return self.n
        else:
            raise StopIteration

迭代器物件產生數值每一次都會經過計算,所以第一次計算就會將屬性 n 乘以 10

22
            self.n = self.n * 10

產生數列的第一個數字就是 10 ,然後回傳這個數字

26
            return self.n

接下來回傳的數字都是乘上 10 ,最後到參數 (parameter) number 為止,所以注意一點,迭代器物件經由計算產生數字,產生的數字到結束條件為止。

有些型態是可迭代的,所謂可迭代的是指有定義 __iter__() 方法,可以放進 for 迴圈 (loop) 使用,序列 (sequence) 如串列 (list) 、字串或 range 都可以用在 for 迴圈,但是序列不是迭代器,因為序列的元素並非由計算產生,而是直接儲存在序列中。

執行部分利用 for 迴圈印出迭代器物件 d 中的所有數字,結果如下

$ python class_demo12.py
10
100
1000
10000
100000
1000000
10000000
100000000
1000000000
10000000000
$

注意上例中的變數 dfor 迴圈之後不能再被使用,不然直譯器 (interpreter) 會發起 StopIteration 例外 (exception) 。

單元 10 介紹的產生器 (generator) 是迭代器的語法糖,簡化了很多寫法,可以用產生器替代簡單的迭代器,所以如果要精確設定迭代器,還是需要自行設計迭代器類別。

以上大致介紹了 Python 設計類別中的資料模型,這些不是全部,先認識這些就目前夠了,接下來我們繼續進一步討論物件導向程式設計 (object oriented programming) 中的繼承 (inheritance) 與多型 (polymorphism) 。

中英文術語對照
布林值Boolean value
屬性attribute
類別class
建構子constructor
資料模型data model
資料型態data type
解構子destructor
字典dictionary
例外exception
函數function
產生器generator
識別字identifier
繼承inheritance
迭代器iterator
直譯器interpreter
串列list
迴圈loop
方法method
物件object
物件導向程式設計object oriented programming
參數parameter
多型polymorphism
作用域scope
序列sequence
字串string
重點整理
1. Python 設計物件採用一套既有的資料模型,因此是用模型來設定物件。
2. __module__ 回傳物件所在的作用域。
3. __name__ 回傳所在函數或類別的識別字。
4. __dict__ 回傳所有內建名稱的配對型態。
5. __str__()__repr__()__format__() 為定義物件的字串形式, __str__() 為一般情況如 print() 中的印出結果, __repr__() 是在 Python Shell 中顯示的字串格式, __format__() 則是定義格式化字串的字串格式。
6. __bool__() 為定義物件的真假性質。
7. __iter__()__next__() 用來定義迭代器物件。
問題與討論
1. Python 的資料模型有哪些預設的屬性及方法?
2. 如果物件要設定字串格式,應該採用 __str__()__repr__()__format__() 中的哪一種?
3. 為什麼物件要定義真假性質?。
4. 迭代器跟產生器有什麼不同?
練習
1. 承接上一單元的練習 4 ,將新程式寫在 exercise1401.py 中,替 Calculator 定義物件的字串表達形式。 參考程式碼
2. 承上題,加入 __repr__() ,其內顯示兩個計算的數字以及所有計算結果。 參考程式碼
3. 承上題,加入 __format__() ,依據參數 op 的計算符號顯示計算等式。 參考程式碼
4. 承上題,加入 __bool__() ,當兩個數字都有值的時候回傳 True參考程式碼
5. 承接上一單元的練習 8 ,將新程式寫在 exercise1405.py 中,替 NumberSequence 定義物件的字串表達形式。 參考程式碼
6. 承上題,加入 __repr__() ,其內顯示屬性以及所有計算結果。 參考程式碼
7. 承上題,加入 __format__() ,依據參數 op"factorial""fibonacci" 顯示相對應的計算結果。 參考程式碼
8. 承上題,加入 __bool__() ,當屬性有值的時候回傳 True參考程式碼
9. 承上題,定義計算階乘的迭代器類別 Factorial參考程式碼
10. 承上題,定義計算費氏數列的迭代器類別 Fibonacci參考程式碼

上一頁 單元 13 - 資料類別、 __init__() 方法與封裝
回 Python 入門指南 5.0 首頁
下一頁 單元 15 - 繼承與多型
回 Python 教材首頁
回程式語言教材首頁