Chapter 06 使 用 類 別
Python 的 類 別 機 制 是 C++ 以 及 Modula-3 的 綜 合 體, 當 我 們 在 使 用 Python 的 類 別 時 並 不 用 去 宣 告 該 類 別 的 型 態, 也 不 用 去 宣 告 這 個 類 別 是 否 為 public 或 private,python 所 有 的 類 別 與 其 包 含 的 成 員 都 是 public 的, 對 於 類 別 (Class) 來 說 最 重 要 的 一 些 特 性 在 Python 程 式 語 言 裡 面 都 有 完 全 的 保 留, 如 最 重 要 的 單 一 繼 承 與 多 重 繼 承, 一 個 衍 生 / 子 類 別 (Derived class) 可 以 覆 載 (Override) 其 所 有 基 礎 類 別 (base class) 的 任 何 方 法 (method), 方 法 (method) 也 可 以 呼 叫 一 個 基 礎 類 別 (base class) 的 同 名 方 法 Python 的 類 別 裡, 所 有 的 函 式 成 員 (member functions) 都 是 virtual 的, 這 裡 也 沒 有 所 謂 的 建 構 函 數 (Constructors) 或 是 解 構 函 數 (Destructors) 的 存 在 Python 的 類 別 宣 告 建 立 一 個 實 例 (Instance) 及 繼 承 都 是 很 快 速 及 簡 單, 在 您 閱 讀 完 本 章 節 後 您 將 學 會 底 下 知 識 : 建 立 一 個 類 別 進 階 的 物 件 導 向 技 巧 使 用 了 解 類 別 變 數 與 實 例 變 數 的 差 異 如 何 動 態 的 新 增 與 刪 除 類 別 實 例 的 方 法 成 員 如 何 動 態 的 新 增 與 刪 除 類 別 實 例 的 屬 性 Python 的 字 串 也 是 物 件 當 我 們 建 立 一 個 字 串 變 數, 其 實 它 就 是 字 串 物 件, 從 dir() 函 數 可 以 看 到 它 繼 承 了 許 多 物 件 的 特 性,dir() 函 數 列 出 我 們 定 義 的 simple 字 串 物 件 可 以 使 用 的 方 法 (method) 成 員 >>> simple = "an object" >>> dir(simple) [' add ', ' class ', ' contains ', ' delattr ', ' doc ', ' eq ', ' ge ', ' getattribute ', ' getitem ', ' getnewargs ', ' getslice ', ' gt ', ' hash ', ' init ', ' le ', ' len ', ' lt ', ' mod ', ' mul ', ' ne ', ' new ', ' reduce ', ' reduce_ex ', ' repr ', 6-2
' rmod ', ' rmul ', ' setattr ', ' str ', 'capitalize', 'center', 'count', 'decode', 'encode', 'endswith', 'expandtabs', 'find', 'index', 'isalnum', 'isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill'] 6.1 定 義 類 別 在 Python 程 式 語 言 裡 要 定 義 一 個 類 別 必 須 使 用 class 敘 述 句, 然 後 在 敘 述 句 後 面 接 著 類 別 的 名 稱, 並 不 用 去 定 義 是 否 為 public 或 private 等, 也 不 用 去 定 義 資 料 型 態, 如 下 定 義 : class 敘 述 句 用 法 : class ClassName: <statements> <next statements> 使 用 類 別 如 底 下 範 例 我 們 宣 告 一 個 簡 單 的 物 件, 名 稱 為 myfirstobj, 在 物 件 裡 面 跟 函 數 一 樣 都 可 以 使 用 備 註 說 明, 我 們 可 以 使 用 doc 屬 性 (attribute) 來 呼 叫 該 物 件 的 備 註 說 明, 如 果 我 們 直 接 呼 叫 物 件 的 名 稱 將 會 得 到 該 物 件 的 名 稱 與 識 別 碼, 如 <class main.myfirstobj at 0x01FF7E10> >>> class myfirstobj: 建 立 一 個 簡 單 的 物 件 在 這 裡 的 是 物 件 的 備 註 說 明, 跟 函 數 一 樣 >>> myfirstobj <class main.myfirstobj at 0x01FF7E10> 6-3
>>> print(myfirstobj. doc ) 建 立 一 個 簡 單 的 物 件 在 這 裡 的 是 物 件 的 備 註 說 明, 跟 函 數 一 樣 6.1.1 使 用 類 別 定 義 好 類 別 後 我 們 必 須 建 立 一 個 物 件 的 實 例 (Instance) 才 能 使 用 物 件, 如 下 範 例 建 立 一 個 物 件 的 實 例, 名 稱 為 o, 我 們 可 以 直 接 呼 叫 o 或 是 利 用 type() 函 數 來 知 道 它 是 物 件 的 實 例 >>> o = myfirstobj() >>> o < main.myfirstobj instance at 0x01FF8F58> >>> type(o) <type 'instance'> Python 3.0 差 異 在 於 instance 變 成 object >>> o < main.myfirstobj object at 0x01019B90> >>> type(o) <class ' main.myfirstobj'> 我 們 可 以 透 過 dir() 函 數 來 了 解 目 前 在 物 件 內 的 方 法 (method) 成 員 >>> dir(o) [' doc ', ' module '] 接 著 我 們 可 以 嘗 試 宣 告 o.mylist = [], 表 示 定 義 一 個 mylist 成 員, 然 後 [] 符 號 代 表 這 個 mylist 成 員 是 序 列 型 態, 定 義 完 後 再 嘗 試 使 用 dir() 函 數 可 以 發 現 mylist 名 稱 被 加 入 清 單 中 >>> o.mylist = [] >>> dir(o) [' doc ', ' module ', 'mylist'] 6-4
接 著 我 們 來 看 剛 才 所 宣 告 的 mylist 有 什 麼 作 用, 如 Figure 6-1 畫 面, 沒 錯 剛 才 的 mylist 繼 承 了 序 列 物 件 的 所 有 (method) 方 法 成 員 Figure 6-1 mylist 成 員 可 以 使 用 的 方 法 (method) 成 員 接 著 我 們 可 以 嘗 試 使 用 mylist 裡 面 的 方 法, 底 下 範 例 是 使 用 extend() 函 數 : >>> o.mylist.extend(["a", "b", "c"]) >>> o.mylist ['a', 'b', 'c'] 使 用 類 別 6.1.2 定 義 類 別 內 的 方 法 (method) 類 別 內 的 方 法 (method) 宣 告 方 式 跟 函 數 一 樣, 您 可 以 想 像 類 別 方 法 就 像 是 將 函 數 放 進 類 別 裡 面, 差 異 性 在 於 函 數 的 引 數 部 份 必 須 要 加 入 self 敘 述 句,self 敘 述 句 就 像 其 他 程 式 語 言 裡 面 的 this 底 下 範 例 定 義 了 一 個 方 法 成 員, 名 稱 為 func, 在 func 雖 然 定 義 了 self 與 name 引 數, 但 是 下 面 在 呼 叫 該 方 法 只 需 要 傳 值 給 name 引 數 就 可 以,self 引 數 是 不 用 理 會 的, 我 們 也 無 法 傳 值 給 它, 但 是 在 定 義 類 別 方 法 成 員 時 是 一 定 要 使 用 的 程 式 碼 :ch601.py #!/usr/bin/env python #coding=utf-8 6-5
# 定 義 myfirstobj 類 別 class myfirstobj: 建 立 一 個 簡 單 的 物 件 在 這 裡 的 是 物 件 的 備 註 說 明, 跟 函 數 一 樣 count = 100 def func(self, name): return "Hello", name # 建 立 instance o = myfirstobj() # 呼 叫 類 別 內 的 attribute print(o.count) # 呼 叫 類 別 內 的 method print(o.func("s")) # 使 用 type 查 看 o.func 的 型 態 print(type(o.func)) 程 式 輸 出 結 果 為 : 100 ('Hello', 's') <type 'method'> 如 果 在 定 義 func 函 數 時 沒 有 給 予 self 敘 述 句 則 會 發 生 TypeError 錯 誤 訊 息, 如 下 : Traceback (most recent call last): File "D: \code\ch601.py", line 20, in <module> print o.func() TypeError: func() takes no arguments (1 given) 6-6
6.1.3 定 義 類 別 內 的 init () 方 法 Python 程 式 語 言 裡 的 類 別 有 一 個 特 別 方 法 (method) 稱 為 init (), 當 建 立 一 個 實 例 (Instance) 的 同 時 類 別 就 會 去 執 行 這 個 函 數, 其 實 這 個 就 像 其 他 程 式 語 言 的 類 別 所 定 義 的 建 構 函 數 (Constructors), 在 範 例 ch602 裡 當 宣 告 n = operator(2) 後 就 會 進 入 operator 類 別 裡 的 init () 方 法, 如 下 : def init (self, x=none): self.x = x self.total = 100 print("run init ()") 另 外 在 這 類 別 裡 有 一 個 實 例 的 全 域 變 數 為 self.total = 100, 當 執 行 plus() 方 法 與 reducr() 方 法 時 就 會 針 對 這 個 全 域 變 數 進 行 遞 增 與 遞 減 程 式 碼 :ch602.py #!/usr/bin/env python #coding=utf-8 使 用 類 別 # 定 義 operator 類 別 class operator: # 這 個 特 殊 的 init () 函 數 會 在 建 立 實 例 Instance 時 就 會 執 行 def init (self, x=none): # 在 這 裡 所 宣 告 的 attributes 前 面 如 果 加 上 self 就 表 示 全 域 變 數 self.x = x self.total = 100 print("run init ()") # 這 個 函 數 會 將 self.total 變 數 遞 增 def plus(self): self.total += self.x return self.total # 這 個 函 數 會 將 self.total 變 數 遞 增 6-7
def reduce(self): self.total -= self.x return self.total # 建 立 Instance, 傳 入 2 表 示 一 次 遞 增 2 或 遞 減 2 n = operator(2) print("run plus method", n.plus()) print("run reduce method", n.reduce()) print("run reduce method", n.reduce()) 程 式 輸 出 結 果 為 : run init () run plus method 102 run reduce method 100 run reduce method 98 6.1.4 定 義 類 別 內 的 屬 性 (attributes) 在 類 別 裡 面 有 所 謂 的 類 別 變 數 (class variable ) 與 實 例 變 數 (Instance variable), 通 常 定 義 在 init () 建 構 函 數 (Constructors) 裡 面 的 稱 為 實 例 變 數, 而 在 類 別 的 init () 建 構 函 數 與 方 法 成 員 之 外 的 變 數 稱 為 類 別 變 數, 實 例 變 數 (Instance variable) 除 了 必 須 使 用 self 敘 述 句 外 也 可 以 使 用 該 類 別 的 名 稱, 假 設 類 別 的 名 稱 為 cos, 然 後 變 數 名 稱 為 x, 那 麼 實 例 全 域 變 數 可 以 設 定 為 self.x 或 是 cos.x, 如 下 範 例 定 義 類 別 稱 為 operator, 所 以 實 例 全 域 變 數 名 稱 也 可 以 寫 成 operator.x 與 operator.total 程 式 碼 :ch603.py #!/usr/bin/env python #coding=utf-8 6-8
# 定 義 operator 類 別 class operator: def init (self, x=none): operator.x = x operator.total = 100 # 這 個 函 數 會 將 self.total 變 數 遞 增 def plus(self): operator.total += operator.x return "increase,", operator.total # 這 個 函 數 會 將 self.total 變 數 遞 增 def reduce(self): operator.total -= operator.x return "decrease, ", operator.total # 建 立 Instance, 傳 入 2 表 示 一 次 遞 增 2 或 遞 減 2 n = operator(2) print(n.plus()) print(n.reduce()) print(n.reduce()) print(n.reduce()) 使 用 類 別 程 式 輸 出 結 果 為 : ('increase,', 102) ('decrease, ', 100) ('decrease, ', 98) ('decrease, ', 96) 6-9
6.1.5 類 別 變 數 (class variable) 與 實 例 變 數 類 別 變 數 (class variable) 與 實 例 變 數 (Instance variable) 定 義 如 下 範 例, 在 範 例 結 果 中 我 們 可 以 清 楚 的 知 道 這 兩 種 變 數 之 間 的 差 異, 類 別 變 數 會 在 建 立 的 實 例 (Instance) 之 間 共 存 如 範 例 中 的 a 與 b, 而 實 例 變 數 並 不 會 共 存, 如 範 例 中 的 c 與 d 程 式 碼 :ch604.py #!/usr/bin/env python #coding=utf-8 class lists: # 類 別 變 數 class_variable = [] def init (self): # self.keywords 是 實 例 變 數 self.instance_variable = [] # 建 立 instance a 與 b a = lists() b = lists() # 給 類 別 變 數 值 a.class_variable.extend([1,2,3,4,5]) # 呼 叫 instance a 與 b print("call a", a.class_variable) print("call b", b.class_variable) # 建 立 instance c 與 d c = lists() d = lists() 6-10
# 給 實 例 變 數 值 c.instance_variable.extend(["a","b","c","d","e"]) # 呼 叫 instance c 與 d print("call c", c.instance_variable) print("call d", d.instance_variable) 程 式 輸 出 結 果 為 : call a [1, 2, 3, 4, 5] call b [1, 2, 3, 4, 5] call c ['a', 'b', 'c', 'd', 'e'] call d [] 6.2 動 態 新 增 類 別 實 例 方 法 成 員 當 我 們 在 建 立 一 個 類 別 物 件 時 都 必 須 先 定 義 好 該 類 別 的 屬 性 方 法 成 員 等, 但 是 您 有 沒 有 想 過, 當 我 們 定 義 完 的 物 件 如 果 在 執 行 期 間 (run-time) 能 不 能 也 能 動 態 的 加 入 新 的 方 法 成 員 或 是 屬 性 到 物 件 裡? 就 好 像 當 我 們 定 義 好 的 物 件 實 例 在 執 行 期 間 時 發 現 本 身 所 定 義 的 方 法 成 員 不 足 夠 時, 然 後 看 到 其 他 的 物 件 有 一 個 不 錯 的 方 法 成 員, 那 麼 我 們 是 不 是 可 以 讓 自 己 的 類 別 物 件 去 學 習 該 物 件 內 的 某 個 方 法 成 員 使 用 類 別 如 範 例 程 式 碼 ch605, 一 開 始 我 們 定 義 了 類 別 A, 然 後 也 定 義 一 個 方 法 成 員, 名 稱 為 sort, 在 執 行 期 間 遇 到 x 序 列, 我 們 想 要 對 這 個 序 列 排 序, 但 是 在 我 們 所 定 義 的 類 別 A 裡 面 的 sort() 方 法 函 數 裡 面 並 沒 有 定 義 任 何 程 式, 接 著 我 們 知 道 在 python 本 身 有 一 個 內 置 的 函 數 稱 為 sorted() 這 個 函 數 是 在 做 排 序 的, 所 以 我 們 決 定 學 習 它, 接 著 重 新 嘗 試 呼 叫 a.sort(), 我 們 發 現 原 本 的 sort 學 習 了 sorted() 的 功 能, 另 外, 我 們 發 現 所 定 義 的 類 別 A 並 沒 有 總 合 運 算 的 功 能, 但 是 我 們 知 道 在 Python 本 身 也 有 一 個 內 置 的 sum() 函 數, 雖 然 我 們 之 前 定 義 的 類 別 A 裡 面 沒 有 sum 這 個 名 稱, 可 以 學 習 嗎? 答 案 是 可 以 的 6-11
程 式 碼 :ch605.py #!/usr/bin/env python #coding=utf-8 # 定 義 一 個 類 別, 名 稱 為 A class A: # 定 義 一 個 方 法 成 員, 名 稱 為 sort def sort(self): pass # 建 立 一 個 實 例 (instance) a = A() # 呼 叫 方 法 成 員 a.sort() print(a.sort()) 在 執 行 期 間 遇 到 x 序 列, 我 們 想 要 對 這 個 序 列 排 序, 但 是 在 我 們 所 定 義 的 類 別 A 裡 面 的 sort() 方 法 函 數 裡 面 並 沒 有 定 義 任 何 程 式. x = [5,2,3,1,4] 接 著 我 們 知 道 在 python 本 身 有 一 個 內 置 的 函 數 稱 為 sorted() 這 個 函 數 是 在 做 排 序 的, 所 以 我 們 決 定 學 習 它. a.sort = sorted 接 著 重 新 嘗 試 呼 叫 a.sort(), 我 們 發 現 原 本 的 sort 學 習 了 sorted() 的 功 能 print(a.sort(x)) 6-12
另 外, 我 們 發 現 所 定 義 的 類 別 A 並 沒 有 總 合 運 算 的 功 能, 但 是 我 們 知 道 在 Python 本 身 也 有 一 個 內 置 的 sum() 函 數, 但 是 我 們 之 前 定 義 的 類 別 A 裡 面 沒 有 sum 這 個 名 稱, 可 以 學 習 嗎? 答 案 是 可 以 的. a.sum = sum 學 習 sum() 函 數 後 我 們 進 行 呼 叫 print(a.sum(x)) 程 式 輸 出 結 果 為 : None [1, 2, 3, 4, 5] 15 使 用 類 別 我 們 針 對 動 態 新 稱 方 法 來 做 更 詳 細 的 介 紹, 底 下 可 以 看 到 定 義 了 一 個 類 別 B, 然 後 這 個 類 別 B 本 身 並 沒 有 NewMethod 方 法 成 員, 後 來 我 們 透 過 new 模 組 內 的 instancemethod() 函 數 來 新 增 NewMethod 方 法 成 員 到 實 例 b 裡 面, 另 外 Python 3.0 已 經 移 除 new 模 組 >>> class B:... def foo(self):... print "foo"... >>> b = B() >>> def NewMethod(self):... print("new method!")... >>> import new 6-13
>>> b.newmethod = new.instancemethod(newmethod, b, B) >>> b.newmethod <bound method B.NewMethod of < main.b instance at 0x01FE9760>> >>> b.newmethod() New method! 6.3 動 態 刪 除 類 別 實 例 方 法 成 員 這 章 節 介 紹 如 何 將 動 態 新 增 的 類 別 實 例 刪 除, 我 們 可 以 使 用 del 敘 述 句 來 刪 除, 如 下 範 例 定 義 類 別, 名 稱 為 B >>> class B:... def init (self):... self.total = 10... def foo(self):... print("foo")... 定 義 NewMethod 函 數 >>> def NewMethod(self):... print("new method!") 建 立 實 例 b >>> b = B() 將 NewMethod 函 數 加 入 實 例 b >>> import new >>> b.newmethod = new.instancemethod(newmethod, b, B) >>> b.newmethod() New method! 使 用 del 敘 述 句 刪 除 NewMethod 函 數 >>> del b.newmethod 6-14
刪 除 後 再 呼 叫 時 就 會 出 現 AttributeError 錯 誤 訊 息, 因 為 實 例 內 已 經 沒 有 NewMethod 函 數 >>> b.newmethod() Traceback (most recent call last): File "<input>", line 1, in <module> AttributeError: B instance has no attribute 'NewMethod' 實 例 本 身 的 變 數 也 是 可 以 刪 除 的, 下 面 我 們 刪 除 了 原 本 在 類 別 內 定 義 的 b.total 實 例 變 數 >>> del b.total 重 新 呼 叫 就 會 出 現 AttributeError 錯 誤 訊 息, 因 為 實 例 b 裡 面 已 經 沒 有 該 變 數 名 稱 >>> print(b.total) Traceback (most recent call last): File "<input>", line 1, in <module> AttributeError: B instance has no attribute 'total' 那 麼 原 本 類 別 裡 面 定 義 的 foo() 方 法 成 員 可 以 刪 除 嗎? 答 案 是 不 行 的, 會 發 生 SyntaxError 錯 誤 訊 息 >>> del b.foo() File "<input>", line 1 SyntaxError: can't delete function call (<input>, line 1) >>> 使 用 類 別 6-15
摘 要 當 我 們 在 使 用 Python 的 類 別 時 並 不 用 去 宣 告 該 類 別 的 型 態, 也 不 用 去 宣 告 這 個 類 別 是 否 為 public 或 private,python 所 有 的 類 別 與 其 包 含 的 成 員 都 是 public 的 在 Python 程 式 語 言 裡 要 定 義 一 個 類 別 必 須 使 用 class 敘 述 句 我 們 可 以 使 用 doc 屬 性 (attribute) 來 呼 叫 該 物 件 的 備 註 說 明 定 義 好 類 別 後 我 們 必 須 建 立 一 個 物 件 的 實 例 (Instance) 才 能 使 用 物 件 類 別 內 的 方 法 (method) 宣 告 方 式 跟 函 數 一 樣, 您 可 以 想 像 類 別 方 法 就 像 是 將 函 數 放 進 類 別 裡 面, 差 異 性 在 於 函 數 的 引 數 部 份 必 須 要 加 入 self 敘 述 句, self 敘 述 句 就 像 其 他 程 式 語 言 裡 面 的 this Python 程 式 語 言 裡 的 類 別 有 一 個 特 別 方 法 (method) 稱 為 init (), 當 建 立 一 個 實 例 (Instance) 的 同 時 類 別 就 會 去 執 行 這 個 函 數 在 類 別 裡 面 有 所 謂 的 類 別 變 數 (class variable) 與 實 例 變 數 (Instance variable), 通 常 定 義 在 init () 建 構 函 數 (Constructors) 裡 面 的 稱 為 實 例 變 數, 而 在 類 別 的 init () 建 構 函 數 與 方 法 成 員 之 外 的 變 數 稱 為 類 別 變 數 類 別 變 數 會 在 建 立 的 實 例 (Instance) 之 間 共 存, 而 實 例 變 數 並 不 會 共 存 我 們 可 以 透 過 new 模 組 內 的 instancemethod() 函 數 來 新 增 方 法 成 員 到 實 例 類 別 裡 面 使 用 del 敘 述 句 來 刪 除 實 例 本 身 的 變 數, 但 是 本 身 已 定 義 好 的 方 法 成 員 是 不 能 刪 除 的 6-16
練 習 6.1 底 下 (1) 為 學 生 類 別, 請 使 用 new 模 組 與 動 態 新 稱 方 法 成 員 的 觀 念 改 變 printdetails 函 數, 並 且 新 增 一 個 email 屬 性 到 實 例 類 別 student1 裡, 並 輸 出 printdetails 函 數 結 果 與 下 面 一 樣 : >>> student1.printdetails("abc@gmail.com") 姓 名 : John 課 程 : ['C201'] email: abc@gmail.com (1) >>> class Student:... 學 生 類 別...... def init (self, name ="Huang", courses =[]):... self.name = name... self.courses = courses... print(" 建 立 實 例 : " + name)...... def printdetails(self):... print(" 姓 名 : ", self.name)... print(" 課 程 : ", self.courses)...... def enroll(self, course):... self.courses.append(course)... >>> student1 = Student("John", ["C201"]) 使 用 類 別 6.2 請 建 立 一 個 類 別, 並 定 義 一 個 全 域 變 數 命 名 為 value, 接 著 再 定 義 一 個 方 法 成 員 命 名 為 pow(x), 並 將 value 值 做 x 次 方 6-17
6-18