📚 今日主题
在面向对象编程中,特殊方法(Special Methods,也称魔术方法)是让自定义类 behave like built-in types 的关键。今天我们来深入探讨如何通过特殊方法实现运算符重载,让你的类更加 Pythonic。
什么是特殊方法?
特殊方法是 Python 中以双下划线开头和结尾的方法,如 __init__、__str__、__add__ 等。它们由 Python 解释器在特定场景下自动调用,无需显式调用。
python
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
"""开发调试时的字符串表示"""
return f"Vector({self.x}, {self.y})"
def __str__(self):
"""用户友好的字符串表示"""
return f"({self.x}, {self.y})"
v = Vector(3, 4)
print(repr(v)) # Vector(3, 4)
print(str(v)) # (3, 4)
print(v) # (3, 4) - 自动调用 __str__
常用特殊方法分类
1. 字符串表示方法
| 方法 | 用途 | 调用时机 |
|---|---|---|
__str__ |
用户友好的字符串 | print(obj), str(obj) |
__repr__ |
开发者调试字符串 | repr(obj), 交互式解释器 |
__format__ |
格式化输出 | format(obj, spec) |
2. 数值运算符重载
python
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
"""向量加法:v1 + v2"""
if isinstance(other, Vector):
return Vector(self.x + other.x, self.y + other.y)
return NotImplemented
def __sub__(self, other):
"""向量减法:v1 - v2"""
if isinstance(other, Vector):
return Vector(self.x - other.x, self.y - other.y)
return NotImplemented
def __mul__(self, scalar):
"""标量乘法:v * 3"""
if isinstance(scalar, (int, float)):
return Vector(self.x * scalar, self.y * scalar)
return NotImplemented
def __rmul__(self, scalar):
"""反向乘法:3 * v"""
return self.__mul__(scalar)
def __abs__(self):
"""向量模长:abs(v)"""
return (self.x ** 2 + self.y ** 2) ** 0.5
def __neg__(self):
"""负向量:-v"""
return Vector(-self.x, -self.y)
# 使用示例
v1 = Vector(2, 3)
v2 = Vector(1, 4)
print(v1 + v2) # (3, 7)
print(v1 - v2) # (1, -1)
print(v1 * 3) # (6, 9)
print(3 * v1) # (6, 9) - 调用 __rmul__
print(abs(v1)) # 3.605551275463989
print(-v1) # (-2, -3)
3. 比较运算符重载
python
from functools import total_ordering
@total_ordering # 自动补全其他比较方法
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
def __eq__(self, other):
"""相等比较:== """
if isinstance(other, Student):
return self.score == other.score
return NotImplemented
def __lt__(self, other):
"""小于比较:<(只需实现这个,@total_ordering 会生成其他的)"""
if isinstance(other, Student):
return self.score < other.score
return NotImplemented
# 使用示例
s1 = Student("Alice", 85)
s2 = Student("Bob", 90)
s3 = Student("Charlie", 85)
print(s1 == s3) # True - 分数相同
print(s1 < s2) # True
print(s1 <= s2) # True - 自动生成
print(s1 > s2) # False - 自动生成
print(s1 != s2) # True - 自动生成
# 可以用于排序
students = [s1, s2, s3]
sorted_students = sorted(students)
print([s.name for s in sorted_students]) # ['Alice', 'Charlie', 'Bob']
4. 容器类型方法
python
class Collection:
def __init__(self):
self._items = []
def __len__(self):
"""支持 len()"""
return len(self._items)
def __getitem__(self, index):
"""支持索引访问:obj[index]"""
return self._items[index]
def __setitem__(self, index, value):
"""支持索引赋值:obj[index] = value"""
self._items[index] = value
def __delitem__(self, index):
"""支持删除:del obj[index]"""
del self._items[index]
def __iter__(self):
"""支持迭代:for item in obj"""
return iter(self._items)
def __contains__(self, item):
"""支持 in 操作符:item in obj"""
return item in self._items
def __repr__(self):
return f"Collection({self._items})"
# 使用示例
coll = Collection()
coll._items = [1, 2, 3, 4, 5]
print(len(coll)) # 5
print(coll[2]) # 3
coll[2] = 30
print(30 in coll) # True
for item in coll:
print(item, end=" ") # 1 2 30 4 5
5. 上下文管理器方法
python
class FileManager:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
"""进入 with 块时调用"""
print(f"Opening {self.filename}")
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
"""退出 with 块时调用"""
print(f"Closing {self.filename}")
self.file.close()
# 返回 True 可以抑制异常,False 或 None 让异常继续传播
return False
# 使用示例
with FileManager("test.txt", "w") as f:
f.write("Hello, World!")
# 自动调用 __exit__ 关闭文件
实战案例:实现一个有理数类
python
from math import gcd
class Rational:
"""有理数类 - 支持分数运算"""
def __init__(self, numerator, denominator=1):
if denominator == 0:
raise ValueError("分母不能为 0")
if denominator < 0:
numerator = -numerator
denominator = -denominator
# 自动约分
common = gcd(abs(numerator), abs(denominator))
self.numerator = numerator // common
self.denominator = denominator // common
def __repr__(self):
return f"Rational({self.numerator}, {self.denominator})"
def __str__(self):
if self.denominator == 1:
return str(self.numerator)
return f"{self.numerator}/{self.denominator}"
def __eq__(self, other):
if isinstance(other, Rational):
return (self.numerator == other.numerator and
self.denominator == other.denominator)
elif isinstance(other, int):
return self.denominator == 1 and self.numerator == other
return NotImplemented
def __add__(self, other):
if isinstance(other, Rational):
num = self.numerator * other.denominator + other.numerator * self.denominator
den = self.denominator * other.denominator
return Rational(num, den)
elif isinstance(other, int):
return Rational(self.numerator + other * self.denominator, self.denominator)
return NotImplemented
def __radd__(self, other):
return self.__add__(other)
def __sub__(self, other):
if isinstance(other, Rational):
num = self.numerator * other.denominator - other.numerator * self.denominator
den = self.denominator * other.denominator
return Rational(num, den)
elif isinstance(other, int):
return Rational(self.numerator - other * self.denominator, self.denominator)
return NotImplemented
def __mul__(self, other):
if isinstance(other, Rational):
return Rational(self.numerator * other.numerator,
self.denominator * other.denominator)
elif isinstance(other, int):
return Rational(self.numerator * other, self.denominator)
return NotImplemented
def __rmul__(self, other):
return self.__mul__(other)
def __truediv__(self, other):
if isinstance(other, Rational):
if other.numerator == 0:
raise ZeroDivisionError("除数不能为 0")
return Rational(self.numerator * other.denominator,
self.denominator * other.numerator)
elif isinstance(other, int):
if other == 0:
raise ZeroDivisionError("除数不能为 0")
return Rational(self.numerator, self.denominator * other)
return NotImplemented
def __float__(self):
"""支持 float() 转换"""
return self.numerator / self.denominator
def __neg__(self):
return Rational(-self.numerator, self.denominator)
# 使用示例
r1 = Rational(1, 2)
r2 = Rational(1, 3)
print(f"{r1} + {r2} = {r1 + r2}") # 1/2 + 1/3 = 5/6
print(f"{r1} - {r2} = {r1 - r2}") # 1/2 - 1/3 = 1/6
print(f"{r1} * {r2} = {r1 * r2}") # 1/2 * 1/3 = 1/6
print(f"{r1} / {r2} = {r1 / r2}") # 1/2 / 1/3 = 3/2
print(f"float({r1}) = {float(r1)}") # float(1/2) = 0.5
print(f"-{r1} = {-r1}") # -1/2 = -1/2
print(f"{r1} == {Rational(2, 4)}: {r1 == Rational(2, 4)}") # True(自动约分后相等)
🎯 最佳实践建议
- 始终实现
__repr__:至少让调试时有可读输出 __str__可选:如果需要用户友好的输出再实现- 返回
NotImplemented:当类型不匹配时,让 Python 尝试反向操作 - 使用
@total_ordering:减少比较方法的编写工作量 - 保持语义一致:重载的运算符应符合用户的直觉预期
- 不要过度重载:只在确实有意义时使用运算符重载
📝 今日总结
特殊方法是 Python 面向对象编程的精髓之一。通过合理使用运算符重载,我们可以让自定义类像内置类型一样自然、优雅。记住:好的 API 设计是让人感觉不到 API 的存在。