Python 特殊方法 (Magic Methods)

Python 特殊方法(Magic Methods)

這篇教學將帶你認識前後雙底線的特殊方法(magic methods/dunder methods)。為什麼 len("abc")a == bprint(obj) 對內建型別都有合理行為?因為它們背後各自呼叫 __len____eq____str__。在自己的類別實作這些方法,你的物件就能像內建型別一樣自然好用。

特殊方法觸發時機
__init__建立物件時(前篇學過)
__str__print(obj)str(obj)
__repr__直接在 REPL 顯示、repr(obj)、容器內顯示
__eq__obj1 == obj2
__len__len(obj)
__add__obj1 + obj2

__str__:讓 print 印出人話

沒定義 __str__ 時,print 出來是看不懂的記憶體位址:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Point(3, 5)
print(p)

執行輸出(位址每次不同):

<__main__.Point object at 0x7f3c2c1d5a90>

加上 __str__

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return f"Point({self.x}, {self.y})"

p = Point(3, 5)
print(p)

執行輸出:

Point(3, 5)

__repr__:給開發者看的版本

__str__ 給使用者看、__repr__ 給開發者除錯看(慣例上是「能重建物件的程式碼樣貌」)。List 裡的物件顯示用的是 __repr__,這是常見的疑惑點:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f"Point({self.x}, {self.y})"

points = [Point(1, 2), Point(3, 4)]
print(points)            # 容器內用 __repr__

執行輸出:

[Point(1, 2), Point(3, 4)]

實務建議:至少定義 __repr__——沒定義 __str__ 時,print 會退而使用 __repr__,一個方法兩種場合都顧到。

__eq__:自訂「相等」的意義

預設的 == 比的是「是不是同一個物件」,不是內容:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

p1 = Point(3, 5)
p2 = Point(3, 5)
print(p1 == p2)      # 內容相同,但預設比較身分

執行輸出:

False

定義 __eq__ 改成比內容:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

p1 = Point(3, 5)
p2 = Point(3, 5)
print(p1 == p2)

執行輸出:

True

__len__ 與 __add__

class Playlist:
    def __init__(self, songs):
        self.songs = songs

    def __len__(self):
        return len(self.songs)

    def __add__(self, other):
        return Playlist(self.songs + other.songs)

a = Playlist(["歌1", "歌2"])
b = Playlist(["歌3"])
merged = a + b           # 觸發 __add__
print(len(merged))       # 觸發 __len__
print(merged.songs)

執行輸出:

3
['歌1', '歌2', '歌3']

常見錯誤

1. print 出 <object at 0x…>

忘了定義 __str____repr__。看到這串就是提醒你補上。

2. __str__ 忘記 return 字串

__str__ 裡用 print 而不是 return,會報 TypeError: __str__ returned non-string。它要「回傳」字串,不是自己印。

3. 定義了 __eq__ 後物件不能放進 set

自訂 __eq__ 會讓物件失去預設的雜湊值(unhashable),放進 set 或當 dict key 會報錯。需要的話再實作 __hash__,或改用下一篇的 dataclass 讓它自動處理。

總結

特殊方法讓自訂類別接上 Python 內建語法:__repr__ 必備(print 與容器顯示都靠它)、__str__ 給使用者、__eq__ 把比較改成比內容、__len____add__ 支援 len() 與 +。覺得這些樣板很繁瑣?下一篇的 dataclass 幫你全部自動生成。

延伸閱讀