当前位置:首页>python>【一起学 Python】第 45 天:高级 OOP 特性之运算符重载

【一起学 Python】第 45 天:高级 OOP 特性之运算符重载

  • 2026-10-10 20:33:07
【一起学 Python】第 45 天:高级 OOP 特性之运算符重载

欢迎来到 Python 学习计划的第 45 天!🎉

昨天我们学习了 __call__ 魔法方法,让对象可以像函数一样被调用。今天,我们将进一步释放对象的潜力,学习 运算符重载(Operator Overloading)。

通过运算符重载,你可以让自定义对象支持 +、-、*、==、[] 等原生运算符。这将让你的代码更简洁、更直观、更"Pythonic"!

一、什么是运算符重载?

1. 定义

运算符重载 是指为自定义类赋予内置运算符(如 +, -, *, == 等)特定含义的过程。本质上,它是通过实现特定的 魔法方法(Magic Methods) 来实现的。

2. 核心原理

当你使用运算符时,Python 解释器会自动调用对应的魔法方法。

运算符

魔法方法

说明

+

__add__(self, other)

加法运算

-

__sub__(self, other)

减法运算

*

__mul__(self, other)

乘法运算

==

__eq__(self, other)

相等比较

<

__lt__(self, other)

小于比较

[]

__getitem__(self, key)

索引访问

len()

__len__(self)

获取长度

3. 为什么要使用?
  • 直观性:v1 + v2 比 v1.add(v2) 更易读。
  • 一致性:让自定义对象 behave 像内置类型(如 list, int)。
  • 简洁性:减少冗余的方法调用代码。

二、算术运算符重载

1. 加法 __add__

实现两个对象相加的逻辑。

class Vector:    def __init__(self, x, y):        self.x = x        self.y = y    def __add__(self, other):        # 返回一个新的 Vector 对象        return Vector(self.x + other.x, self.y + other.y)    def __repr__(self):        return f"Vector({self.x}, {self.y})"v1 = Vector(2, 3)v2 = Vector(4, 5)v3 = v1 + v2  # 自动调用 v1.__add__(v2)print(v3)  # Vector(6, 8)

2. 乘法 __mul__

可以实现对象与标量相乘,或对象与对象相乘。

class Vector:    # ... __init__, __add__ ...    def __mul__(self, scalar):        # 向量乘以标量        return Vector(self.x * scalar, self.y * scalar)v = Vector(2, 3)print(v * 3)  # Vector(6, 9)

3. 反向运算 __radd__

如果左操作数不支持该运算,Python 会尝试调用右操作数的 __radd__。

class Vector:    # ...    def __radd__(self, other):        # 处理 other + self 的情况        return self.__add__(other)# 即使左边不是 Vector,也能处理result = (10, 20) + v  # 如果实现了相应逻辑
💡 关键点:算术运算通常应该返回新对象,而不是修改原对象(除非明确设计为原地修改 __iadd__)。

三、比较运算符重载

默认情况下,对象比较的是内存地址。通过重载比较方法,可以实现值比较。

1. 相等 __eq__

class Money:    def __init__(self, amount, currency="CNY"):        self.amount = amount        self.currency = currency    def __eq__(self, other):        # 比较金额和货币是否相同        return self.amount == other.amount and self.currency == other.currencym1 = Money(100, "CNY")m2 = Money(100, "CNY")m3 = Money(100, "USD")print(m1 == m2)  # True (值相等)print(m1 == m3)  # False (货币不同)

2. 大小比较 __lt__, __le__, __gt__, __ge__

只需实现 __lt__ 和 __eq__,Python 的 @functools.total_ordering 可以自动生成其他比较方法。

from functools import total_ordering@total_orderingclass Student:    def __init__(self, name, score):        self.name = name        self.score = score    def __eq__(self, other):        return self.score == other.score    def __lt__(self, other):        return self.score < other.scores1 = Student("Alice", 90)s2 = Student("Bob", 85)print(s1 > s2)  # True (自动推导)print(s1 >= s2) # True (自动推导)

四、容器/序列运算符重载

让自定义对象像列表或字典一样支持索引、长度检查等操作。

1. 长度 __len__

支持内置函数 len()。

class Collection:    def __init__(self, items):        self.items = items    def __len__(self):        return len(self.items)c = Collection([1, 2, 3, 4, 5])print(len(c))  # 5

2. 索引访问 __getitem__ 和 __setitem__

支持 obj[key] 读写操作。

class MyList:    def __init__(self, data):        self.data = data    def __getitem__(self, index):        return self.data[index]    def __setitem__(self, index, value):        self.data[index] = value    def __repr__(self):        return f"MyList({self.data})"ml = MyList([10, 20, 30])print(ml[1])      # 20 (调用 __getitem__)ml[1] = 200       # 调用 __setitem__print(ml)         # MyList([10, 200, 30])

3. 迭代支持

实现了 __getitem__ 且索引从 0 开始连续的对象,通常自动支持迭代(for x in obj)。

for item in ml:    print(item)  # 10, 200, 30

五、实战案例:自定义金额类

结合算术和比较运算符,实现一个安全的金额计算类。

class Money:    def __init__(self, amount, currency="CNY"):        self.amount = amount        self.currency = currency    def __add__(self, other):        if self.currency != other.currency:            raise ValueError("货币单位不同,无法相加")        return Money(self.amount + other.amount, self.currency)    def __sub__(self, other):        if self.currency != other.currency:            raise ValueError("货币单位不同,无法相减")        return Money(self.amount - other.amount, self.currency)    def __eq__(self, other):        return self.amount == other.amount and self.currency == other.currency    def __repr__(self):        return f"Money({self.amount}, {self.currency})"# 使用wallet1 = Money(100, "CNY")wallet2 = Money(50, "CNY")total = wallet1 + wallet2print(total)  # Money(150, CNY)# wallet3 = Money(10, "USD")# total = wallet1 + wallet3  # ❌ 抛出 ValueError

六、常见误区与注意事项

1. 语义一致性

运算符的行为应符合直觉。不要让 + 执行减法,不要让 == 修改状态。

# ❌ 不推荐def __add__(self, other):    self.amount -= other.amount  # 加法变成了减法且修改了自身    return self

2. 类型检查

在运算前检查 other 的类型,避免 AttributeError。

def __add__(self, other):    if not isinstance(other, Money):        return NotImplemented  # 让 Python 尝试 other.__radd__    # ...

3. 可变 vs 不可变

  • 不可变对象(如 tuple, str):运算应返回新对象。
  • 可变对象(如 list):可以提供 __iadd__ (+=) 原地修改,但 __add__ (+) 仍应返回新对象。

4. 不要过度重载

只重载有意义的运算符。不要让对象支持所有运算符,这会增加维护成本。

七、实战练习

练习 1:实现复数类

创建一个 Complex 类,支持复数的加法和乘法。

  • 属性:real (实部), imag (虚部)
  • 方法:__add__, __mul__, __repr__
公式:
class Complex:    def __init__(self, real, imag):        self.real = real        self.imag = imag    def __add__(self, other):        return Complex(self.real + other.real, self.imag + other.imag)    def __mul__(self, other):        real = self.real * other.real - self.imag * other.imag        imag = self.real * other.imag + self.imag * other.real        return Complex(real, imag)    def __repr__(self):        return f"Complex({self.real}, {self.imag})"c1 = Complex(1, 2)c2 = Complex(3, 4)print(c1 + c2)  # Complex(4, 6)print(c1 * c2)  # Complex(-5, 10)

练习 2:实现可索引的栈

创建一个 Stack 类,支持 push, pop, 以及通过索引访问元素 stack[i] 和获取长度 len(stack)。

class Stack:    def __init__(self):        self.items = []    def push(self, item):        self.items.append(item)    def pop(self):        return self.items.pop()    def __len__(self):        return len(self.items)    def __getitem__(self, index):        return self.items[index]    def __repr__(self):        return f"Stack({self.items})"s = Stack()s.push(1)s.push(2)s.push(3)print(len(s))    # 3print(s[1])      # 2 (栈底第二个元素)print(s.pop())   # 3print(s)         # Stack([1, 2])

八、总结

知识点

说明

运算符重载

通过魔法方法让对象支持内置运算符

算术运算

__add__, __sub__, __mul__ 等

比较运算

__eq__, __lt__ 等,支持 @total_ordering

容器操作

__len__, __getitem__, __setitem__

最佳实践

保持语义一致,返回新对象,检查类型

注意事项

不要滥用,避免过度复杂化

📌 明日预告:property 装饰器

明天我们将进入 高级 OOP 特性第五天!

  • 主题:@property 装饰器 - 属性访问控制
  • 核心问题:
    1. 如何将方法变成属性调用?obj.attr vs obj.method()
    2. Getter, Setter, Deleter 如何实现?
    3. 如何保护私有属性不被随意修改?
    4. 计算属性(Computed Property)有什么用?
    5. 它与 Java 的 Getter/Setter 有什么区别?

💡 提前思考:如果有一个 Circle 类,area 应该是一个属性还是一个方法?如何通过 @property 让它像属性一样访问?

掌握运算符重载,让你的对象更自然、更强大!继续加油!🚀

最新文章

随机文章