特殊方法与运算符重载

📚 今日主题

在面向对象编程中,特殊方法(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(自动约分后相等)

🎯 最佳实践建议

  1. 始终实现 __repr__ :至少让调试时有可读输出
  2. __str__ 可选:如果需要用户友好的输出再实现
  3. 返回 NotImplemented:当类型不匹配时,让 Python 尝试反向操作
  4. 使用 @total_ordering:减少比较方法的编写工作量
  5. 保持语义一致:重载的运算符应符合用户的直觉预期
  6. 不要过度重载:只在确实有意义时使用运算符重载

📝 今日总结

特殊方法是 Python 面向对象编程的精髓之一。通过合理使用运算符重载,我们可以让自定义类像内置类型一样自然、优雅。记住:好的 API 设计是让人感觉不到 API 的存在

相关推荐
xht08322 小时前
PHP vs Python:编程语言终极对决
开发语言·python·php
2401_879693872 小时前
使用Python控制Arduino或树莓派
jvm·数据库·python
查古穆2 小时前
python进阶-推导式
开发语言·python
☆5663 小时前
如何为开源Python项目做贡献?
jvm·数据库·python
m0_560396473 小时前
使用Python进行PDF文件的处理与操作
jvm·数据库·python
SEO-狼术3 小时前
Improve Navigation with In-Cell Hyperlinks
python·pdf
2301_816651223 小时前
用Python监控系统日志并发送警报
jvm·数据库·python
飞Link3 小时前
Python Pydantic V2 核心原理解析与企业级实战指南
开发语言·python