
在 Python 中,你經常會看到以雙底線 (Double Underscore) 開頭和結尾的方法,例如 __init__。因為唸起來像 “Double Under”,所以開發者常暱稱它們為 Dunder Methods,或者更帥氣的稱呼——魔術方法 (Magic Methods)。
這些方法會在特定的情況下被 Python 直譯器自動呼叫。透過實作它們,我們可以讓自訂的物件表現得像 Python 的內建型別 (如 List, Int, String) 一樣自然。
1. 字串表示:__str__ 與 __repr__
當我們 print() 一個物件時,通常會看到像 <__main__.Car object at 0x10a2b...> 這樣看不懂的訊息。我們可以透過覆寫這兩個方法來改善。
__str__: 給使用者看的,力求可讀性 (Readability)。__repr__: 給開發者看的,力求明確性 (Unambiguous),最好能用來重新產生該物件。
class Car:
def __init__(self, brand, model):
self.brand = brand
self.model = model
def __str__(self):
return f"{self.brand} {self.model}"
def __repr__(self):
return f"Car(brand='{self.brand}', model='{self.model}')"
my_car = Car("Tesla", "Model Y")
print(str(my_car)) # Tesla Model Y (呼叫 __str__)
print(repr(my_car)) # Car(brand='Tesla', model='Model Y') (呼叫 __repr__)
最佳實踐:至少要寫
__repr__。如果沒有寫__str__,Python 會自動退而求其次呼叫__repr__。
2. 運算子多載 (Operator Overloading):__add__
想要讓兩個物件像數字一樣相加嗎?實作 __add__ 即可。
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
# 回傳一個新的 Vector 物件
return Vector(self.x + other.x, self.y + other.y)
def __repr__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(2, 4)
v2 = Vector(3, 1)
v3 = v1 + v2
print(v3) # Vector(5, 5)
常用運算子對應表:
+:__add__-:__sub__*:__mul__==:__eq__<:__lt__
3. 容器行為:__len__ 與 __getitem__
想讓你的物件能用 len() 測長度,或者像 List 一樣用 obj[0] 取值嗎?
class Deck:
def __init__(self):
self.cards = ['A', '2', '3', '4', '5']
def __len__(self):
return len(self.cards)
def __getitem__(self, position):
return self.cards[position]
deck = Deck()
print(len(deck)) # 5
print(deck[0]) # A
print(deck[-1]) # 5 (因為 list 支援負索引,這裡直接轉發給 list)
只要實作了 __getitem__,物件甚至自動支援 for 迴圈迭代!
4. 可呼叫物件:__call__
讓物件像函數一樣被呼叫。
class Multiplier:
def __init__(self, factor):
self.factor = factor
def __call__(self, x):
return x * self.factor
double = Multiplier(2)
print(double(10)) # 20 (這看起來像函數呼叫,其實是物件!)
這在編寫裝飾器或需要保存狀態的回呼函數 (Callback) 時非常有用。
5. 資源管理:__enter__ 與 __exit__
這是 with 語句背後的魔法,我們將在第 19 章 (Context Managers) 詳細介紹。
6. 總結
魔術方法是讓 Python 程式碼變得優雅、直觀的關鍵。但請記得:能力越強,責任越大。不要為了炫技而過度使用運算子多載 (例如把 + 定義成「相減」),這會讓維護你程式碼的人崩潰。
本章重點回顧:
- init: 建構子。
- str / repr: 字串表示法。
- add / eq: 運算子行為定義。
- len / getitem: 讓物件像容器一樣。
- call: 讓物件像函數一樣被呼叫。
下一章,我們將學習 Python 類別設計中的進階技巧,包括 @property (屬性封裝)、@classmethod 與 @staticmethod,讓你的類別介面更乾淨安全!
延伸閱讀: