魔术方法
魔术方法(Magic Methods),也称为双下划线方法(Dunder Methods),是 Python 对象模型的核心。它们让自定义类能够响应内置函数和运算符,使得用户定义的对象可以像内置类型一样自然工作。
字符串表示:str 与 repr
__str__ 和 __repr__ 控制对象的字符串表示形式,但服务于不同场景。
__repr__ 的目标是无歧义:返回的字符串应当尽可能准确地表示对象,理想情况下可以作为代码重新创建该对象。__str__ 的目标是可读性:返回面向用户的友好描述。
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f'Point({self.x!r}, {self.y!r})'
def __str__(self):
return f'({self.x}, {self.y})'
p = Point(3, 4)
print(str(p)) # (3, 4) —— 调用 __str__
print(repr(p)) # Point(3, 4) —— 调用 __repr__
print(p) # (3, 4) —— print 调用 __str__
如果类未定义 __str__,str() 和 print() 会回退到 __repr__。反之则不会回退。因此,至少实现 __repr__ 是良好的实践。如果 __repr__ 本身已经足够友好,也可以只定义 __repr__ 而不定义 __str__。
比较操作
__eq__ 定义相等比较(==),__lt__ 定义小于(<),__le__ 定义小于等于(<=),__gt__ 和 __ge__ 定义大于相关操作。__ne__ 默认由 __eq__ 推导,通常不需要显式定义。
class Score:
def __init__(self, value):
self.value = value
def __eq__(self, other):
if not isinstance(other, Score):
return NotImplemented
return self.value == other.value
def __lt__(self, other):
if not isinstance(other, Score):
return NotImplemented
return self.value < other.value
s1 = Score(85)
s2 = Score(90)
print(s1 == s2) # False
print(s1 < s2) # True
print(s1 <= s2) # True —— Python 自动由 __lt__ 和 __eq__ 推导
当比较操作返回 NotImplemented 时,Python 会尝试调用另一个操作数的对应反射方法(如 __gt__),或最终回退到默认行为。这比直接抛出 TypeError 更友好,允许不同类型的对象参与比较。
如果定义了 __eq__ 但未定义 __hash__,类的实例将变为不可哈希,无法放入 set 或作为 dict 的键。若对象是可变的,通常应将 __hash__ 设为 None 来明确禁止哈希:
class MutablePoint:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
if isinstance(other, MutablePoint):
return self.x == other.x and self.y == other.y
return NotImplemented
__hash__ = None # 显式声明不可哈希
容器协议
实现容器协议让自定义类支持 len()、[] 索引、in 成员测试和 for 迭代。
class Stack:
def __init__(self):
self._items = []
def push(self, item):
self._items.append(item)
def __len__(self):
return len(self._items)
def __getitem__(self, index):
"""支持 s[0], s[-1], s[1:3] 等"""
return self._items[index]
def __contains__(self, item):
return item in self._items
def __iter__(self):
return iter(self._items)
s = Stack()
s.push('a')
s.push('b')
s.push('c')
print(len(s)) # 3
print(s[0]) # a
print(s[-1]) # c
print('b' in s) # True
print(list(s)) # ['a', 'b', 'c']
__getitem__ 接收的 index 不仅可以是整数,还可以是 slice 对象。如果未实现 __iter__ 但实现了 __getitem__,Python 会尝试通过从 0 开始递增索引来迭代对象(旧式迭代协议)。
可调用对象:call
实现 __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(callable(double)) # True
callable(obj) 检查对象是否可调用。如果类定义了 __call__,callable() 返回 True。
算术运算符
算术魔术方法让对象支持 +、-、* 等运算符。以 __add__ 为例,当执行 a + b 时,Python 首先调用 a.__add__(b);如果返回 NotImplemented,再尝试 b.__radd__(a)。
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
if isinstance(other, Vector):
return Vector(self.x + other.x, self.y + other.y)
return NotImplemented
def __radd__(self, other):
return self.__add__(other)
def __repr__(self):
return f'Vector({self.x!r}, {self.y!r})'
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Vector(4, 6)
对于数值类型,还应考虑实现 __iadd__(+=)等原地操作。如果未定义原地方法,a += b 会回退到 a = a + b。
迭代器协议
迭代器协议要求对象实现 __iter__ 返回自身,以及 __next__ 返回下一个元素或在结束时抛出 StopIteration。
class Countdown:
def __init__(self, start):
self.start = start
def __iter__(self):
return self
def __next__(self):
if self.start <= 0:
raise StopIteration
self.start -= 1
return self.start + 1
for n in Countdown(5):
print(n, end=' ') # 5 4 3 2 1
更常见的做法是让 __iter__ 返回一个生成器,利用生成器自动保存状态的能力:
class BetterCountdown:
def __init__(self, start):
self.start = start
def __iter__(self):
n = self.start
while n > 0:
yield n
n -= 1
常用魔术方法速查
| 类别 | 方法 | 触发方式 |
|---|---|---|
| 生命周期 | __init__, __del__ | 构造、析构 |
| 字符串 | __str__, __repr__, __format__ | str(), repr(), format() |
| 比较 | __eq__, __lt__, __le__, __gt__, __ge__, __ne__ | ==, <, <=, >, >=, != |
| 算术 | __add__, __sub__, __mul__, __truediv__, __pow__ | +, -, *, /, ** |
| 反射算术 | __radd__, __rsub__, ... | 当左操作数不支持时 |
| 容器 | __len__, __getitem__, __setitem__, __delitem__, __contains__ | len(), [], del [], in |
| 迭代 | __iter__, __next__ | for, iter(), next() |
| 可调用 | __call__ | obj() |
| 哈希 | __hash__ | hash(), set, dict 键 |
| 布尔 | __bool__ | bool(), if 条件 |
| 属性访问 | __getattr__, __getattribute__, __setattr__, __delattr__ | 属性读写删除 |
魔术方法的设计哲学是:不需要记忆全部名称,而是理解"想让对象支持什么操作,就实现对应的 __xxx__"。查阅官方文档的 Data Model 章节可获得完整列表。