特殊方法与运算符重载

📚 今日主题

在面向对象编程中,特殊方法(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 的存在

相关推荐
临沂GEO2 小时前
GEO搜索优化科普|正规地理位置流量运营入门指南
大数据·人工智能·python·流量运营
大模型码小白2 小时前
Spring AI Tool 实现自然语言操作 MySQL 数据库详解
服务器·开发语言·数据库·人工智能·python·mysql·spring
荷蒲3 小时前
【小白量化Qbuddy】利用AI学习中文Python
人工智能·python·学习
数据狐(Datafox)5 小时前
京东商品列表API技术解析与落地应用(含标准 JSON 示例)
java·大数据·前端·人工智能·python·数据分析·json
量化吞吐机5 小时前
2026年下半年手工交易规则走向量化表达,产品该先接住哪一步
人工智能·python
Metaphor6926 小时前
使用 Python 读取和删除 Excel 文件属性
python·excel
迷迭香yy6 小时前
Python构建转融券多空识别系统从接口到因子的全流程 IG50免费开源股票数据API接口
开发语言·python·开源
月光船幽幽7 小时前
真实即粗糙,光滑是伪造
人工智能·python
一次旅行7 小时前
Attention机制从数学到工程:拆解缩放点积+多头注意力|附可运行PyTorch实现与踩坑指南
人工智能·pytorch·python
泡干脆面就番茄7 小时前
NumPy 科学计算完全指南:从数组创建到广播机制
python·numpy