認識物件導向
-
物件導向的優點是物件可以在不同的應用程式中被重複使用,
Windows本身就是一個物件導向的例子。下面是幾個常見的名詞:- 物件(
object) 或實體 (instance) 就像在生活中所看到的各種物體,在Python中,物件是資料與程式碼的組合,它可以是整個應用程式或應用程式的一部分。 - 屬性 (
attribute) 或成員變數 (member variable) 是用來描述物件的特質。 - 方法 (
method) 或成員方法 (member function) 是用來定義物件的動作。 - 類別 (
class) 是物件的分類,就像物件的藍圖或樣板,隸屬於相同類別的物件具有相同的屬性與方法,但屬性的值則不一定相同。


- 物件(
-
物件導向程式設計 (OOP,Object Oriented Programming) 主要有下列幾個特點:
- 封裝 (
encapsulation):物件導向程式設計將資料與用來處理資料的函式放在一起成為一個類別,稱為「封裝」,著重於物件與物件之間的操作。 - 繼承 (
inheritance):繼承指的是從既有的類別定義出新的類別,這個既有的類別叫做父類別 (parent class),而這個新的類別則叫做子類別 (child class、subclass)。 - 多型 (
polymorphism):多型指的是當不同的物件收到相同的訊息時,會以各自的方法來做處理。

Python中的所有資料都是物件 (object),而物件的型別定義於類別 (class),類別就像物件的藍圖或樣板,裡面定義了物件的資料,以及用來操作物件的函式,前者稱為屬性 (attribute),後者稱為方法 (method)。- 物件是類別的實體 (
instance),我們可以根據相同的類別建立多個物件,這個建立物件的動作稱為實體化 (instantiation)。 Python中的物件都有編號 (id)、型別 (type) 與值 (value),可以透過下列函式取得這些資訊:id(x)type(x)print(x)
- 封裝 (
類別與物件
較完整的應用程式通常由許多類別組成,Python 其實是一種物件導向(Object Oriented Programming) 程式語言,可以建立類別後再根據類別建立物件。
-
建立類別
-
建立類別
以class可以建立類別,類別名稱的第一個字元建議使用大寫字元。語法:class 類別名稱():例如: 建立類別
Animal,其中「()」也可以省略,寫成「class Animal:」class Animal(): -
類別的屬性和方法
類別中通常會建立屬性(attribute)和方法(method),提供物件使用,類別中的屬性其實就是一般的變數,而方法則是函式,但在類別中不以變數和函式稱呼,而是稱為屬性和方法。定義的方法中第一個參數必須是self,第二個以後的參數則可依實際需要增加或省略。以類別名稱即可建立物件
(object)。語法:物件=類別()然後以物件執行其屬性和方法。
物件.屬性 物件.方法()
【案例】
建立類別Animal,並在類別建立name屬性和sing方法。程式碼: class01.py
# 定義類別 class Animal(): #定義類別 name="小鳥" #定義屬性,向右縮排至少一個空白,同時縮排要對齊 def sing(self):#定義方法 print("很會唱歌!") # 以類別名稱即可建立物件(object) # 依據類別 Animal 建立物件 bird, 取得 name 屬性和執行 sing() 方法 bird=Animal() # 建立一個名叫 bird 的 Animal 物件 print(bird.name) # 小鳥 bird.sing() # 很會唱歌! -
-
類別的建構式(Constructor)
當我們透過類別實例化(建立)一個物件時,Python 會自動呼叫一個特殊方法來初始化這個物件,這個方法就是__init__(),也就是所謂的建構式,建立建構式的語法:def __init__(self[, 參數1, 參數2, ...]): # 建立物件屬性 self.屬性名稱 = 參數self:代表「正在被建立的這個物件本身」,必須放在第一個參數的位置。- 預設建構式:若定義類別時未寫
__init__(),Python 會自動提供一個不帶自訂參數的預設建構式(即def __init__(self): pass)。
-
類別的解構式(Destructor)
相對建構式是在物件「誕生」時執行,解構式(
__del__) 則是在物件「消逝/被銷毀」時自動執行的特殊方法。當一個物件不再被使用(例如程式執行完畢、或者手動使用
del刪除該物件)時,Python 的垃圾回收機制(Garbage Collector)會清理該物件所占用的記憶體空間,並在清理前自動呼叫__del__(),建立解構式的語法:def __del__(self): # 清理資源的程式碼(如關閉檔案、斷開資料庫連線等)
【案例】
程式碼: class02.py
class Animal: # 定義類別 def __init__(self, name): # 建構式:物件誕生時執行 self.name = name # 定義屬性 print(f"【誕生】{self.name} 被建立好了!") def sing(self): # 定義方法 print(self.name + ",很會唱歌!") def __del__(self): # 解構式:物件銷毀時執行 print(f"【銷毀】{self.name} 離開了,記憶體空間已釋放。") # 1. 以 Animal 類別建立一個名叫鸚鵡的 bird 物件 bird = Animal("鸚鵡") # 2. 存取屬性與呼叫方法 print(bird.name) # 鸚鵡 bird.sing() # 鸚鵡,很會唱歌! # 3. 手動刪除物件(觸發解構) print("準備執行 del bird ...") del bird print("程式執行完畢。")
【預期輸出結果】
【誕生】鸚鵡 被建立好了!
鸚鵡
鸚鵡,很會唱歌!
準備執行 del bird ...
【銷毀】鸚鵡 離開了,記憶體空間已釋放。
程式執行完畢。-
屬性初始值的設定
class01.py第3列「name="小鳥"」可以設定name的初始值,但無法在建立物件時就直接初始化,如果將初始化的動作放在__init__()建構式中,這樣我們就可以在建立物件時,透過參數設定其初始值。
【案例】
建立物件bird預設屬性name="鸚鵡"、age=1。
程式碼: class03.py
class Animal(): # 定義類別 def __init__(self, name,age): self.name = name # 定義屬性 self.age = age def sing(self): # 定義方法 print(self.name + str(self.age)+"歲,很會唱歌!") def grow(self,year): # 定義方法 self.age += year bird =Animal("鸚鵡",1) # 以 Animal 類別,建立一個名叫鸚鵡、1歲大的 bird物件 bird.grow(1) # 長大1歲 bird.sing() # 鸚鵡2歲,很會唱歌!
程式說明
⬜ 2
def __init__(self, name, age): 接收兩個參數name和age,以self.name和self.age設定物件屬性。
⬜ 5def sing(self):印出名稱、年齡與訊息。
⬜ 7def grow(self, year):讓年齡增加year歲。
⬜ 9bird = Animal("鸚鵡", 1):以Animal類別建立一個名叫鸚鵡、1 歲大的bird物件。
⬜ 10bird.grow(1):讓bird長大 1 歲,年齡變為 2。
⬜ 11bird.sing():印出鸚鵡2歲,很會唱歌!。
建構式與解構式比較表
| 特性 | 建構式 (init) | 解構式 (del) |
|---|---|---|
| 執行時機 | 建立物件時自動執行 | 物件被銷毀/記憶體釋放時自動執行 |
| 主要用途 | 設定屬性初始值、分配資源 | 釋放外部資源(如關閉檔案、資料庫連線) |
| 預設行為 | 未定義時由 Python 提供無動作預設值 | 未定義時由 Python 提供無動作預設值 |
| 常態呼叫 | bird = Animal("鸚鵡") | del bird 或程式結束時自動呼叫 |
-
匿名物件
我們可以用一個很貼切的生活比喻來理解匿名物件(Anonymous Object):- 具名物件(有變數名稱):像是在動物園裡養一隻鸚鵡,給牠取名叫
bird(指派給變數)。你可以今天餵牠bird.grow(1),明天叫牠唱歌bird.sing(),因為你有一個固定名稱(變數)持續關心與追蹤這隻特定的鸚鵡。 - 匿名物件(沒有變數名稱):像是你在路上臨時遇到一隻野生鸚鵡,你當下對牠做了一件事(比如聽牠唱一句歌),做完之後你沒有把牠帶回家命名,牠就飛走了。下一次你再呼叫同樣的類別,Python 會建立一隻全新的物件,跟前一隻毫無關係。
【案例】
程式碼: anonymous.py
class Animal(): # 定義類別 def __init__(self, name,age): self.name = name # 定義屬性 self.age = age def sing(self): # 定義方法 print(self.name + str(self.age) + "歲,很會唱歌!") def grow(self,year): # 定義方法 self.age += year # bird = Animal("鸚鵡",1) #以 Animal 類別,建立一個名叫鸚鵡、1歲大的 bird 物件 # bird.grow(1) # 長大1歲 # bird.sing() # 鸚鵡2歲,很會唱歌! Animal("鸚鵡",1).grow(1) # 不用 bird,建立了「第一隻」匿名鸚鵡,讓牠長大 1 歲。做完這行,這隻鸚鵡就被 Python 扔掉了! Animal("鸚鵡",1).sing() # 又建立了「第二隻」全新的匿名鸚鵡,牠還是 1 歲!因為沒有用變數(如
bird = ...)把第一隻鸚鵡「綁住/存下來」,所以執行完 第13行grow(1)後,第一隻鸚鵡就消失在記憶體中了;第14行呼叫sing()時,Python 是重新現場打造了一隻全新的 1 歲鸚鵡。什麼時候才需要用「匿名物件」?
匿名物件通常用在「一次性使用、用完即丟」的情境。你不需要特地花一個變數名稱去記住它,只需要用它一次性的功能或結果。 - 具名物件(有變數名稱):像是在動物園裡養一隻鸚鵡,給牠取名叫
類別封裝
在 class03.py 程式中,可以 bird.age 存取 age 屬性,因此就可以設定 bird.age=-1 設定 bird 年齡為 -1 歲,這樣直接從外部設定 年齢<0 的方式其實並不合理,因此必須對 age 屬性作適度的保護。
類別中可以讓外部引用的屬性稱為共用 (public) 屬性、方法稱為共用方法,在 class03.py 程式中,年齡 age 應該以 bird.grow() 方法增加,不可以從外部以 bird.age 直接設定。
Python 提供私用 (private) 屬性和私用方法,這種私用屬性和私用方法只有類別內內部可以使用,類別外部並無法使用,這樣的觀念稱為 封裝(encapsulation)。
在屬性和方法前面加上 __ (兩個 __ 字元),就成為 私用屬性 和 方法。例如: 類別中建立 __name、__age 屬性和 __sing方法。
【案例】
程式碼: class04.py
class Animal(): # 定義類別 def __init__(self, name, age): self.__name = name # 定義私用屬性 self.__age = age def __sing (self) : # 定義私用方法 print(self.__name + str(self.__age),end= " 歲,很會唱歌,") def talk(self): # 定義共用方法 self.__sing() # 使用私用方法 print("也會模仿人類說話!") bird=Animal("灰鸚鵡",2) # 以Animal類別,建立一個名叫灰鸚鵡、2歲大的 bird物件 bird.talk() # 灰鸚鵡2歲,很會唱歌,也會模仿人類說話! bird.__age = -1 # 設定無效 bird.talk() # 灰鸚鵡2歲,很會唱歌,也會模仿人類說話! #bird.__sing() # 執行出現錯誤
類別外部並無法使用私用屬性和方法,因此如果在第 14列行 bird.__age =-1 無效,第16列執行bird.__sing()將會產生錯誤。
類別繼承
類別可以繼承,被繼承的類別稱為父類別 (parent class) 或基底類別(base class),繼承的類別稱為子類別 (child class)或衍生類別 (derived class),子類別可以繼承父
類別中所有共用屬性和共用方法。在程式設計時,請注意父類別必須放在子類別的前面。
-
建立子類別
建立子類別的語法:class 類別名稱(父類別):例如: 建立類別
Bird繼承Animal類別,其中Animal是父類別,Bird是子類別。class Bird(Animal):子類別會繼承父類別的所有共用屬性和方法,也可以建立屬於自己的屬性和方法。

例如: 建立父類別
Animal,包含name、fly共用屬性和方法,再建立子類別Bird繼承Animal類別,並在Bird類別中建立另一個共用方法sing。【案例】
請設計一個簡單的動物類別繼承結構,包含父類別Animal與子類別Bird:- 建立父類別
Animal:- 建構式:接收一個參數
name,並將其設定為物件屬性self.name。 - 方法
fly():呼叫時會印出"{name}很會飛!"(例如:小白鴿很會飛!)。
- 建構式:接收一個參數
- 建立子類別
Bird(繼承自Animal):- 覆寫建構式
__init__():接收參數name,但在設定屬性時,必須將其修改覆寫為"粉紅色" + name。 - 新增方法
sing():呼叫時會印出"{name}也愛唱歌!"(例如:粉紅色小鸚鵡也愛唱歌!)。
- 覆寫建構式
- 實例化與測試:
- 使用
Animal類別建立一個名為"小白鴿"的物件pigeon,並呼叫其fly()方法。 - 使用
Bird類別建立一個名為"小鸚鵡"的物件parrot,並分別呼叫其fly()(繼承自父類別)與sing()(子類別自訂)方法。
- 使用
【預期輸出結果】
小白鴿很會飛! 粉紅色小鸚鵡很會飛! 粉紅色小鸚鵡也愛唱歌!程式碼: class05.py
class Animal(): # 定義父類別 def __init__(self, name): self.name = name # 定義共用屬性 def fly(self): # 定義共用方法 print(self.name + "很會飛!") class Bird(Animal): # 定義子類別 def __init__(self, name): self.name = "粉紅色" + name #覆寫父類別的建構式 def sing(self): #定義子類別的方法 print(self.name + "也愛唱歌!") pigeon = Animal("小白鴿")#以 Animal 類別,建立一個名叫小白鴿的 pigeon 物件 pigeon.fly() #小白鴿很會飛! parrot = Bird("小鸚鵡") #以 Bird 類別,建立一個名叫小鸚鵡的 parrot 物件 parrot.fly() #粉紅色小鸚鵡很會飛! parrot.sing() #粉紅色小鸚鵡也愛唱歌!程式說明
⬜ 7Bird繼承Animal類別,也繼承了name、fly共用屬性和方法。
⬜ 9 覆寫父類別的建構式。
⬜ 10-11 建立專屬於Bird子類別的方法sing。
⬜ 13 以Animal類別,建立一個名叫小白鴿的pigeon物件。
⬜ 14 執行Animal父類別的fly方法。
⬜ 16 以Bird類別,建立一個名叫小鸚鵡的parrot物件。
⬜ 17 執行繼承Animal父類別的fly方法。
⬜ 18 執行Bird子類別的sing方法。 - 建立父類別
-
子類別和父類別擁有相同的屬性和方法
有的時侯會碰到子類別和父類別擁有相同的屬性和方法,此時子類別會先尋找子類別中是否有此名稱的屬性和方法,如果有找到就使用子類別的的屬性和方法,否則就使用父類別的的屬性和方法。子類別也可用
super()方法執行父類別的方法。
【案例】
建立類別Bird繼承類別Animal,子類別Bird再以super()方法覆寫__init__()和fly()方法。【預期輸出結果】
小白鴿很會飛! 2歲小鸚鵡很會飛!程式碼: class06.py
class Animal(): #定義父類別 def __init__(self,name): self.name = name # 定義共用屬性 def fly(self): #定義共用方法 print(self.name+"很會飛!") class Bird(Animal): #定義子類別 def __init__(self,name,age): super().__init__(name) # 執行父類別的__init__()方法 self.age = age #定義子類別共用屬性 def fly(self): #定義子類別共用方法 print(str(self.age)+"歲", end="") super().fly() # 執行父類別的 fly方法 if __name__ == "__main__": pigeon = Animal("小白鴿") # 以Animal類別建立一個名叫小白鴿的pigeon物件 pigeon.fly() # 小白鴿很會飛! parrot = Bird("小鸚鵡",2) # 以Bird類別建立一個名叫小鸚鵡、2歲大的parrot物件 parrot.fly() # 2歲小鸚鵡很會飛!程式說明
⬜ 8-10def __init__(self,name,age): 接收兩個參數,其中age為年齡,super().__init__(name)執行父類別的__init__()方法。
⬜ 11-13def fly(self):以super().fly()執行父類別的fly方法。
⬜ 15-16 執行的是Animal父類別的fly方法。
⬜ 18-19 執行的是Bird子類別的的fly方法。在 Python 程式碼中,
if __name__ == "__main__":是一個非常經典且重要的結構。它的主要用途是:判斷當前這個 Python 檔案是被「直接執行」還是被「當成模組(module)匯入(import)」。-
__name__是什麼?
__name__是 Python 內建的一個特殊變數(系統變數):- 當直接執行該檔案時:Python 會自動將這個檔案的
__name__變數賦值為"__main__"。 - 當該檔案被其他檔案
import時:__name__變數的值會變成「該檔案的檔名」(不含.py副檔名)。
- 當直接執行該檔案時:Python 會自動將這個檔案的
-
用途:防止「匯入時自動執行測試程式碼」
以你的程式為例,假設這個檔案叫做class06.py:class Animal: ... class Bird(Animal): ... if __name__ == "__main__": pigeon = Animal("小白鴿") pigeon.fly() ...-
情況 A(直接執行
class06.py):__name__等於"__main__",條件成立,縮排裡面的測試程式碼(建立pigeon、parrot並印出訊息)會正常執行。 -
情況 B(在另一個檔案
main.py中匯入它):# main.py import class06 # 匯入類別 my_bird = class06.Bird("文鳥", 1)當
main.py執行import class06時,Python 會把class06.py從頭到尾讀取一遍以載入類別定義。
此時在class06.py內部,__name__的值是"class06"(不是"__main__"),因此if條件不成立,底下的測試程式碼完全不會被執行!
-
-
何時必須/建議加入這一行?
情況 是否建議加入? 原因與說明 撰寫可重用的模組/函式庫 必須加入 當你寫的 .py檔案主要是定義 Class 或 Function,打算給其他專案import時,將範例或測試程式碼放在這段判斷式內。單一獨立執行的主程式入口 建議加入 作為整個專案的「程式執行入口」(Main Entry Point),讓程式結構更清晰、規範。 單一檔案的腳本(Script)或作業 可以不加 如果這個 .py檔只是自己交作業或一次性腳本,絕對不會被其他檔案import,不加程式也能正常運作,但加上是養成良好的寫作習慣。
-
-
鏈狀繼承(Chained Inheritance)

- 結構:單向縱向延伸(一對一的直線鏈條,像「祖孫關係」)。
- 特徵:一個子類別只有一個直接父類別,但透過一層層向上繼承,擁有整條鏈條上所有祖先類別的特徵。
- 關係圖:
【案例】
程式碼: ChainedInheritance.py
class A: x = 1 class B(A): y = 2 class C(B): z = 3 obj = C() print("x屬性的值為", obj.x) print("y屬性的值為", obj.y) print("z屬性的值為", obj.z)【說明】
obj是類別C的實例。obj.z:在C類別本身找到z = 3。obj.y:C本身沒有y,順著繼承鏈向上至父類別B找到y = 2。obj.x:C與B都沒有x,繼續向上至祖父類別A找到x = 1。
-
多重繼承(Multiple Inheritance)

-
橫向整合:與「鏈狀繼承(祖孫關係)」不同,多重繼承就像是孩子同時繼承了父親與母親的基因與技能。
-
技能組合:子類別可以同時擁有來自
父類別1、父類別2到父類別n的所有屬性與方法,讓子類別具備複合式的功能。
Python支援多重繼承,一個子類別可以繼承多個父類別,語法:class 子類別名稱(父類別1,父類別2,...,父類別n)程式碼: MultipleInheritance.py
class Father: # 父類別 1 def drive(self): print("會開車") class Mother: # 父類別 2 def cook(self): print("會做飯") # 多重繼承:Child 同時繼承了 Father 和 Mother class Child(Father, Mother): pass kid = Child() kid.drive() # 來自 Father kid.cook() # 來自 Mother程式說明
⬜ 10Child同時繼承了Father和Mother,因此擁有drive()和cook()兩個方法。
⬜ 14kid.drive()執行的是繼承自Father類別的drive方法。
⬜ 15kid.cook()執行的是繼承自Mother類別的cook方法。
當子類別與多個父類別擁有相同名稱的方法時,Python 會依照 MRO(Method Resolution Order)順序尋找,即先找Child,再依繼承順序找Father,最後找Mother。 -
多型
前面不同類別中擁有相同的方法名稱,這樣的觀念稱為多型(polymorphism),但其實多型不一定要有繼承關係,它的好處是同一個方法名稱卻可以產生不同的功能。
【案例】
定義 Bird 繼承Animal類別,再定義另一個 Plane類別,這3個類別都擁有fly方法。
程式碼: class07.py
class Animal: # 定義父類別 def fly(self): print("時速 20公里!") class Bird(Animal): # 定義子類別 def fly(self, speed): # 覆寫父類別方法 print("時速 " + str(speed) + "公里!") class Plane: # 定義類別 def fly(self): # 方法1 print("時速 1000 公里!") def fly_mile(self, speed): print("時速 " + str(speed) + "英哩!") animal = Animal() animal.fly() # 時速 20公里! bird = Bird() bird.fly(60) # 時速 60公里! plane = Plane() plane.fly() # 時速 1000 公里! plane.fly_mile(5) # 時速 5英哩!
程式說明
⬜ 16-17 執行的是Animal 類別的 fly 方法。
⬜ 19-20 執行的是Bird 類別的 fly方法。
⬜ 22-24 執行的是 Plane 類別的fly與fly_mile方法。
取得父類別的私用屬性
基於對私用屬性的保護,類別之外並無法取得類別內的私用屬性,包括它的子類別也無法讀取,如果一定非取得不可,就只能以「return 私用屬性」的方式,將私用屬性傳回。 例如: 在子類別中以super().getEye()取得父類別的私用屬性「self.__eye」。
程式碼: getPrivateAttribute.py
# getPrivateAttribute.py class Father(): #定義父類別 def __init__(self,name): self.name = name self.__eye="黑色" # 定義私用屬性 def getEye(self): # 定義共用方法傳回私用屬性 return self.__eye class Child(Father): # 定義子類別 def __init__(self,name,eye): super().__init__(name) self.eye=eye self.fatherEye=super().getEye() # 取得私用屬性 joe = Child("小華","棕色") #建立子類別物件 joe print(joe.name+"眼睛是"+joe.eye+",他的父親則是"+joe.fatherEye) # 執行結果:小華眼睛是棕色,他的父親則是黑色
過載(Overloading)
在物件導向程式設計(OOP)中,「多型(Polymorphism)」 與 「過載(Overloading)」 是兩個常常被放在一起討論,但層次不同的概念:
核心結論: 過載(Overloading) 是一種實現 多型(Polymorphism) 的「手段或形式」之一。
多型 是觀念:指的是「同一個介面/方法名稱,可以根據情境呈現不同的行為或處理不同的資料」。
過載 是實現方式:指的是「在同一個類別中,定義多個名稱相同但參數數量或型態不同的方法」。
但在 Python 中,這兩者的關係非常特殊,主要原因如下:
1. 傳統語言中的關係(如 C++ / Java)
在傳統靜態型別語言中,多型主要分為兩種型態:
- 編譯期多型(Compile-time Polymorphism) ➔ 透過「過載 (Overloading)」實現
- 編譯器在執行前,會根據你傳入的參數數量與型態,自動決定呼叫哪一個同名方法。
- 執行期多型(Runtime Polymorphism) ➔ 透過「覆寫 (Overriding / 繼承)」實現
- 子類別重寫父類別的方法,物件在執行時根據自己的實際型態展現不同行為。
2. Python 中「過載」與「多型」的特殊關係
Python 是一門動態型別語言(Dynamic Typing),且遵循鴨子型態(Duck Typing)哲學:「如果它走起來像鴨子,叫起來像鴨子,那它就是鴨子」。
這導致 Python 對這兩者的處理方式與其他語言不同:
① Python 原生不支援「語法上的方法過載」
如上一題所提到的,如果在 Python 的同一個 class 中寫了兩個同名方法,後者會直接蓋掉前者。因此,Python 無法像 Java 那樣直接透過寫多個同名函式來實現「過載多型」。
② Python 用「極度彈性的參數」取代了過載
雖然 Python 沒有傳統的過載語法,但它透過以下機制,用單一方法就達成了過載的效果:
- 預設參數(Default Arguments):
def add(x, y=0) - 不定長度參數(
*args,**kwargs) - 型態檢查與動態判斷
這意味著:在 Python 中,你不需要寫多個過載方法來達成多型,寫一個彈性極高的方法即可。
1. 使用 Default Arguments
【案例】
使用預設參數(Default Arguments),透過檢查傳入的參數數量或使用 isinstance() 判斷型態,在同一個方法內部寫邏輯判斷。
【預期輸出結果】
單一數字處理結果:15
數字相加結果:30
字串串接結果:Hello - World程式碼: overloading_defaults_argus.py
class Calculator: # 透過預設參數 y=None, z=None 處理不同個數 def add(self, x, y=None): # 情況 1:如果傳入兩個參數,做相加 if y is not None: # 依據型態不同做不同處理 # 變數 x 是否為字串型別 if isinstance(x, str) and isinstance(y, str): return f"字串串接結果:{x} - {y}" return f"數字相加結果:{x + y}" # 情況 2:如果只傳入一個參數,預設 +10 return f"單一數字處理結果:{x + 10}" # ==================== 測試執行 ==================== calc = Calculator() # 1. 傳入 1 個參數 print(calc.add(5)) # 輸出:單一數字處理結果:15 # 2. 傳入 2 個數字型態參數 print(calc.add(10, 20)) # 輸出:數字相加結果:30 # 3. 傳入 2 個字串型態參數 print(calc.add("Hello", "World")) # 輸出:字串串接結果:Hello - World
2. 使用 *args
*args 讓函式可以接受任意數量的位置參數(positional arguments),不需要事先固定參數個數。
【案例】
使用*args,透過檢查傳入的參數數量或使用 isinstance() 判斷型態,在同一個方法內部寫邏輯判斷。
【預期輸出結果】
單一數字處理結果:15
數字相加結果:30
字串串接結果:Hello - World程式碼: overloading_args.py
class Calculator: def add(self, *args): # 情況 1:如果傳入 2 個參數 if len(args) == 2: x, y = args[0], args[1] # 依據型態不同做不同處理 if isinstance(x, str) and isinstance(y, str): return f"字串串接結果:{x} - {y}" return f"數字相加結果:{x + y}" # 情況 2:如果傳入 1 個參數,預設 +10 elif len(args) == 1: x = args[0] return f"單一數字處理結果:{x + 10}" # 防呆機制:處理傳入 0 個或 3 個以上參數的情況 else: return "錯誤:不支援此參數數量!" # ==================== 測試執行 ==================== calc = Calculator() # 1. 傳入 1 個參數 print(calc.add(5)) # 輸出:單一數字處理結果:15 # 2. 傳入 2 個數字型態參數 print(calc.add(10, 20)) # 輸出:數字相加結果:30 # 3. 傳入 2 個字串型態參數 print(calc.add("Hello", "World")) # 輸出:字串串接結果:Hello - World
3. 使用內建裝飾器 @singledispatchmethod(依型態分派)
如果你希望程式碼看起來更像傳統語言的多型,可以使用 Python 內建 functools 模組提供的裝飾器,來達到「依據傳入參數的資料型態自動找對應方法」的效果:
【案例】
使用 @singledispatchmethod 裝飾器時,主要是依據第一個參數的資料型態來決定呼叫哪一個方法。
因為傳入兩個字串和傳入數字時的行為不同,我們可以針對 int(數字)與 str(字串)分別註冊不同的處理函式:
【預期輸出結果】
單一數字處理結果:15
數字相加結果:30
字串串接結果:Hello - World程式碼: overloading_singledispatchmethod.py
from functools import singledispatchmethod class Calculator: # 1. 預設方法:當第一個參數是 int (或預設型態) 時執行 @singledispatchmethod def add(self, x, y=None): # 情況 1:傳入兩個數字 -> 做數字相加 if y is not None: return f"數字相加結果:{x + y}" # 情況 2:只傳入一個數字 -> 預設 +10 return f"單一數字處理結果:{x + 10}" # 2. 註冊方法:當第一個參數 x 為 str (字串型態) 時自動分派到此方法 @add.register def _(self, x: str, y: str): return f"字串串接結果:{x} - {y}" # ==================== 測試執行 ==================== calc = Calculator() # 1. 傳入 1 個參數 (int) print(calc.add(5)) # 輸出:單一數字處理結果:15 # 2. 傳入 2 個數字型態參數 (int, int) print(calc.add(10, 20)) # 輸出:數字相加結果:30 # 3. 傳入 2 個字串型態參數 (str, str) print(calc.add("Hello", "World")) # 輸出:字串串接結果:Hello - World
程式說明
@singledispatchmethod:
- 放在主要的
add方法上方,將其設定為「分派器(Dispatcher)」。- 當傳入的第一個參數
x是整數(例如5或10)時,會直接執行這個預設方法。@add.register:
- 透過在註冊語法中標註型態提示
x: str,告誡 Python:「當第一個參數是字串時,改執行這個專屬的 function」。- 方法名稱使用底線
_即可,因為呼叫時一律使用calc.add(...)。
③ Python 的多型核心在於「鴨子型態」與「覆寫(Overriding)」
Python 的多型不需要依賴過載,而是依賴物件是否具備同名的方法。
只要不同的物件擁有相同名稱的方法,Python 就能直接呼叫,完全不需要關心物件的類別是否相同或是否有繼承關係:
程式碼: overriding2.py
class Dog: def make_sound(self): print("汪汪!") class Cat: def make_sound(self): print("喵喵!") class Car: def make_sound(self): print("叭叭!") # 多型展現:不管是狗、貓還是汽車,只要有 make_sound() 就能運作 def process_sound(obj): obj.make_sound() # 這就是 Python 最典型的多型表現(鴨子型態) process_sound(Dog()) # 汪汪! process_sound(Cat()) # 喵喵! process_sound(Car()) # 叭叭!
觀念對比總結表
| 比較維度 | 多型(Polymorphism) | 過載(Overloading) |
|---|---|---|
| 定義性質 | 物件導向的核心觀念與特性 | 實現多型的其中一種語法手段 |
| 主要目的 | 讓不同的物件能響應相同的呼叫 | 讓同一個名稱的方法能接受不同的參數組合 |
| Python 的支援度 | 完全支援(主要靠鴨子型態與方法覆寫) | 原生不支援(需改用 *args、預設參數或裝飾器模擬) |
| 主要實現場合 | 不同的類別間(或繼承結構中) | 同一個類別內部 |
介面 Interface
Python 本身沒有像 Java、C# 那種正式的 interface 關鍵字
但! Python 仍然可以用好幾種方式達成 「介面」 的效果:
方法: 使用 abc 模組(最正式、最接近 interface)
Python 的
from abc import ABC, abstractmethod可以創建「抽象類別」(abstract class),等同於介面。
範例:
-
定義介面
from abc import ABC, abstractmethod class Animal(ABC): # 介面(抽象類別) @abstractmethod def sound(self): pass -
實作介面
class Dog(Animal): def sound(self): return "汪汪" class Cat(Animal): def sound(self): return "喵喵" -
使用
d = Dog() print(d.sound())
類別應用
範例: 計算面積
定義Rectangle、Triangle 兩個類別,父類別 Rectangle定義共用屬性 width、height
和area()方法計算矩形面積。Triangle 子類別繼承 Rectangle 類別並增加一個計算三角形面積的方法area2()。
程式碼: myClass.py
class Rectangle(): #定義父類別 def __init__(self, width,height): self.width = width # 定義共用屬性 self.height= height # 定義共用屬性 def area(self): # 定義共用方法 return self.width * self.height class Triangle(Rectangle): # 定義子類別 def area2(self): # 定義子類別的共用方法 return (self.width * self.height)/2 triangle = Triangle(5,6) # 建立 triangle 物件 print("矩形面積=",triangle.area()) # 30 print("三角形面積=",triangle.area2()) # 15.0
程式說明
⬜ 1-6 建立父類別 Rectangle。
⬜ 2-4 建立父類別建構式。
⬜ 5-6 建立 area 方法計算矩形面積。
⬜ 8-10 建立子類別 Triangle 繼承 Rectangle 類別。
⬜ 9-10 建立 area2 方法計算三角形面積。
⬜ 12 Triangle(5,6)建立子類別的物件triangle,並初始化。
⬜ 13-14 計算矩形面積、三角形面積。