Python 特殊方法 (Magic Methods)
特殊方法(也稱為魔術方法或 dunder methods)是以雙底線開頭和結尾的方法,讓你可以自訂類別的行為。
常用特殊方法
| 方法 | 用途 |
|---|---|
__init__ | 建構子,初始化物件 |
__str__ | 字串表示(給人看) |
__repr__ | 官方字串表示(給開發者看) |
__len__ | 定義 len() 的行為 |
__getitem__ | 定義索引存取 obj[key] |
__setitem__ | 定義索引設值 obj[key] = value |
__iter__ | 定義迭代行為 |
__call__ | 讓物件可以像函數一樣呼叫 |
__init__ 建構子
在建立物件時自動呼叫:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
p = Person("Alice", 25)
print(p.name) # Alice
__str__ 和 __repr__
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
# 給人看的字串表示
return f"Point at ({self.x}, {self.y})"
def __repr__(self):
# 給開發者看的字串表示,通常可以重建物件
return f"Point({self.x}, {self.y})"
p = Point(3, 4)
print(str(p)) # Point at (3, 4)
print(repr(p)) # Point(3, 4)
print(p) # Point at (3, 4)(print 預設使用 __str__)
如果只定義
repr,它也會用於 str。建議至少定義 repr。比較運算子
| 方法 | 運算子 |
|---|---|
__eq__ | == |
__ne__ | != |
__lt__ | < |
__le__ | <= |
__gt__ | > |
__ge__ | >= |
class Money:
def __init__(self, amount):
self.amount = amount
def __eq__(self, other):
return self.amount == other.amount
def __lt__(self, other):
return self.amount < other.amount
def __le__(self, other):
return self.amount <= other.amount
m1 = Money(100)
m2 = Money(200)
m3 = Money(100)
print(m1 == m3) # True
print(m1 < m2) # True
print(m2 <= m1) # False
使用 functools.total_ordering 可以只定義 __eq__ 和一個比較方法,自動產生其他方法:
from functools import total_ordering
@total_ordering
class Money:
def __init__(self, amount):
self.amount = amount
def __eq__(self, other):
return self.amount == other.amount
def __lt__(self, other):
return self.amount < other.amount
# 現在 <=, >, >= 都可以用了
算術運算子
| 方法 | 運算子 |
|---|---|
__add__ | + |
__sub__ | - |
__mul__ | * |
__truediv__ | / |
__floordiv__ | // |
__mod__ | % |
__pow__ | ** |
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return Vector(self.x - other.x, self.y - other.y)
def __mul__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)
def __repr__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Vector(4, 6)
print(v2 - v1) # Vector(2, 2)
print(v1 * 3) # Vector(3, 6)
__len__ 和 __bool__
class Playlist:
def __init__(self):
self.songs = []
def add(self, song):
self.songs.append(song)
def __len__(self):
return len(self.songs)
def __bool__(self):
return len(self.songs) > 0
playlist = Playlist()
print(len(playlist)) # 0
print(bool(playlist)) # False
playlist.add("Song 1")
playlist.add("Song 2")
print(len(playlist)) # 2
print(bool(playlist)) # True
if playlist:
print("Playlist has songs")
__getitem__ 和 __setitem__
讓物件支援索引存取:
class Matrix:
def __init__(self, rows, cols):
self.data = [[0] * cols for _ in range(rows)]
self.rows = rows
self.cols = cols
def __getitem__(self, pos):
row, col = pos
return self.data[row][col]
def __setitem__(self, pos, value):
row, col = pos
self.data[row][col] = value
def __repr__(self):
return '\n'.join(str(row) for row in self.data)
m = Matrix(3, 3)
m[0, 0] = 1
m[1, 1] = 2
m[2, 2] = 3
print(m[1, 1]) # 2
print(m)
__iter__ 和 __next__
讓物件可以迭代:
class Countdown:
def __init__(self, start):
self.start = start
def __iter__(self):
self.current = self.start
return self
def __next__(self):
if self.current <= 0:
raise StopIteration
self.current -= 1
return self.current + 1
for num in Countdown(5):
print(num) # 5, 4, 3, 2, 1
__call__
讓物件可以像函數一樣呼叫:
class Multiplier:
def __init__(self, factor):
self.factor = factor
def __call__(self, value):
return value * self.factor
double = Multiplier(2)
triple = Multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
print(double(10)) # 20
應用:建立裝飾器
class CountCalls:
def __init__(self, func):
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"Call {self.count} of {self.func.__name__}")
return self.func(*args, **kwargs)
@CountCalls
def greet(name):
return f"Hello, {name}!"
print(greet("Alice")) # Call 1 of greet \n Hello, Alice!
print(greet("Bob")) # Call 2 of greet \n Hello, Bob!
__enter__ 和 __exit__
實作 context manager(用於 with 語句):
class Timer:
def __enter__(self):
import time
self.start = time.time()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
import time
self.end = time.time()
print(f"Elapsed time: {self.end - self.start:.4f} seconds")
return False # 不抑制例外
with Timer():
import time
time.sleep(1)
# Elapsed time: 1.0012 seconds
完整範例
class Fraction:
def __init__(self, numerator, denominator):
if denominator == 0:
raise ValueError("Denominator cannot be zero")
self.numerator = numerator
self.denominator = denominator
self._simplify()
def _gcd(self, a, b):
while b:
a, b = b, a % b
return a
def _simplify(self):
gcd = self._gcd(abs(self.numerator), abs(self.denominator))
self.numerator //= gcd
self.denominator //= gcd
if self.denominator < 0:
self.numerator *= -1
self.denominator *= -1
def __repr__(self):
return f"Fraction({self.numerator}, {self.denominator})"
def __str__(self):
return f"{self.numerator}/{self.denominator}"
def __add__(self, other):
num = self.numerator * other.denominator + other.numerator * self.denominator
den = self.denominator * other.denominator
return Fraction(num, den)
def __sub__(self, other):
num = self.numerator * other.denominator - other.numerator * self.denominator
den = self.denominator * other.denominator
return Fraction(num, den)
def __mul__(self, other):
return Fraction(self.numerator * other.numerator,
self.denominator * other.denominator)
def __truediv__(self, other):
return Fraction(self.numerator * other.denominator,
self.denominator * other.numerator)
def __eq__(self, other):
return (self.numerator == other.numerator and
self.denominator == other.denominator)
def __float__(self):
return self.numerator / self.denominator
# 使用
f1 = Fraction(1, 2)
f2 = Fraction(1, 3)
print(f1 + f2) # 5/6
print(f1 - f2) # 1/6
print(f1 * f2) # 1/6
print(f1 / f2) # 3/2
print(float(f1)) # 0.5