Python 入門指南 5.0
單元 14 - 資料模型、特別屬性與迭代器
Python 內建很多特殊的屬性 (attribute) 或方法 (method) ,設定專門的屬性或方法可以讓類別 (class) 具有通用或特定功能,這些屬性或方法在識別字 (identifier) 的前後用連續兩條底線圍起來
__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 來執行,下圖可以看到執行結果
以上在印出變數 d 及 format(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 |
$ |
注意上例中的變數 d 在 for 迴圈之後不能再被使用,不然直譯器 (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 。 參考程式碼 |