一、开篇:让+、==对你的对象也有效

当你写3 + 5时,Python实际调用的是(3).__add__(5)。当你写"hello" == "world"时,调用的是"hello".__eq__("world")。Python的每一个运算符背后都有一个魔法方法——你可以为自己的类实现这些方法,让+、-、==、<这些运算符对你的对象生效。

先看最直观的例子:

class Vector:
    """二维向量——支持加减"""
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, other):
        """v1 + v2"""
        return Vector(self.x + other.x, self.y + other.y)

    def __sub__(self, other):
        """v1 - v2"""
        return Vector(self.x - other.x, self.y - other.y)

    def __repr__(self):
        return f"Vector({self.x}, {self.y})"

v1 = Vector(3, 4)
v2 = Vector(1, 2)
print(v1 + v2)  # Vector(4, 6)
print(v1 - v2)  # Vector(2, 2)

二、算术运算符

2.1 双目运算符

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 __mul__(self, factor):
        """金额 × 数量"""
        if isinstance(factor, (int, float)):
            return Money(self.amount * factor, self.currency)
        return NotImplemented  # 不支持的操作返回NotImplemented

    def __truediv__(self, divisor):
        """金额 ÷ 数量"""
        if divisor == 0:
            raise ZeroDivisionError("除数不能为0")
        return Money(self.amount / divisor, self.currency)

    def __floordiv__(self, divisor):
        """金额 // 数量"""
        return Money(self.amount // divisor, self.currency)

    def __repr__(self):
        return f"Money({self.amount:.2f}, {self.currency})"

# 使用
price = Money(100)
quantity = 3
total = price * quantity
print(total)        # Money(300.00, CNY)
print(total / 2)    # Money(150.00, CNY)
print(Money(500) - Money(200))  # Money(300.00, CNY)

2.2 反向运算符和增强赋值

class Vector:
    def __init__(self, x, y):
        self.x, self.y = x, y

    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

    def __radd__(self, other):
        """反向加法:当左操作数不支持加法时调用"""
        # 例如:tuple + Vector
        if isinstance(other, (tuple, list)) and len(other) == 2:
            return Vector(self.x + other[0], self.y + other[1])
        return NotImplemented

    def __iadd__(self, other):
        """增强赋值 += """
        self.x += other.x
        self.y += other.y
        return self  # 必须返回self!

    def __repr__(self):
        return f"Vector({self.x}, {self.y})"

v = Vector(3, 4)
# __radd__的例子
# result = (1, 2) + v  # 元组不支持 + Vector,Python调用v.__radd__((1,2))
# print(result)  # Vector(4, 6)

# __iadd__的例子
v += Vector(1, 1)
print(v)  # Vector(4, 5) —— v自身被修改了

三、比较运算符

3.1 等于和不等于

class Person:
    def __init__(self, name, id_number):
        self.name = name
        self.id_number = id_number

    def __eq__(self, other):
        """== —— 通过身份证号判断是否同一个人"""
        if not isinstance(other, Person):
            return NotImplemented
        return self.id_number == other.id_number

    def __ne__(self, other):
        """!= —— 通常直接反转__eq__即可"""
        result = self.__eq__(other)
        if result is NotImplemented:
            return result
        return not result

    def __hash__(self):
        """定义了__eq__就必须定义__hash__(用于set和dict的键)"""
        return hash(self.id_number)

    def __repr__(self):
        return f"Person({self.name}, {self.id_number})"

p1 = Person("张三", "110101199001011234")
p2 = Person("张三别名", "110101199001011234")  # 同一身份证号
p3 = Person("李四", "110101199501011234")

print(p1 == p2)  # True —— 同一个人!
print(p1 == p3)  # False
print(p1 != p3)  # True

# 可以放入集合——根据__hash__和__eq__去重
people = {p1, p2, p3}
print(len(people))  # 2 —— p1和p2被视为同一个

3.2 大小比较

from functools import total_ordering

@total_ordering  # 只需定义__eq__和__lt__,其余自动生成
class Version:
    """语义化版本号"""
    def __init__(self, major, minor, patch):
        self.major = major
        self.minor = minor
        self.patch = patch

    def __eq__(self, other):
        return (self.major, self.minor, self.patch) == \
               (other.major, other.minor, other.patch)

    def __lt__(self, other):
        return (self.major, self.minor, self.patch) < \
               (other.major, other.minor, other.patch)

    def __repr__(self):
        return f"v{self.major}.{self.minor}.{self.patch}"

versions = [Version(2, 0, 1), Version(1, 5, 3),
            Version(2, 1, 0), Version(1, 0, 0)]
print(sorted(versions))
# [v1.0.0, v1.5.3, v2.0.1, v2.1.0]

# @total_ordering自动生成了:__le__, __gt__, __ge__

四、容器类运算符

4.1getitem、setitem、len

class Matrix:
    """简单的二维矩阵——支持[][]索引"""
    def __init__(self, rows, cols, default=0):
        self.rows = rows
        self.cols = cols
        self._data = [[default] * cols for _ in range(rows)]

    def __getitem__(self, index):
        """matrix[row][col]"""
        return self._data[index]

    def __setitem__(self, index, value):
        """matrix[row] = [val1, val2, ...]"""
        if len(value) != self.cols:
            raise ValueError(f"每行必须有{self.cols}个元素")
        self._data[index] = list(value)

    def __len__(self):
        """len(matrix) → 行数"""
        return self.rows

    def __contains__(self, value):
        """value in matrix —— 检查矩阵中是否有某个值"""
        return any(value in row for row in self._data)

    def __repr__(self):
        return "\n".join(" ".join(f"{v:3d}" for v in row) for row in self._data)

# 使用
m = Matrix(3, 4)
m[0] = [1, 2, 3, 4]
m[1] = [5, 6, 7, 8]
m[2] = [9, 10, 11, 12]
print(m[0][2])     # 3
print(f"行数: {len(m)}")  # 3
print(7 in m)      # True

4.2 完整运算符速查表

# 算术运算符
# __add__(+), __sub__(-), __mul__(*), __truediv__(/), __floordiv__(//)
# __mod__(%), __pow__(**), __divmod__()
# __radd__, __rsub__, ... (反向运算符)
# __iadd__, __isub__, ... (增强赋值 += 等)

# 比较运算符
# __eq__(==), __ne__(!=), __lt__(<), __le__(<=), __gt__(>), __ge__(>=)

# 一元运算符
# __neg__(-), __pos__(+), __abs__(abs()), __invert__(~)

# 容器运算符
# __len__(len), __getitem__([]), __setitem__([]=), __delitem__(del [])
# __contains__(in), __iter__(for)

# 类型转换
# __int__(int), __float__(float), __bool__(bool), __str__(str), __bytes__(bytes)

# 其他
# __call__(obj()), __enter__/__exit__(with), __hash__(hash)

五、总结

运算符重载让你的对象用起来像内置类型一样自然。但不要滥用——只在你真的需要、且行为符合直觉时才重载。

核心要点:

  1. 运算符背后是魔法方法——+→__add__,==→__eq__
  2. __eq__和__hash__要一起定义——否则set/dict出问题
  3. @total_ordering——定义__eq__和__lt__就自动生成全部比较方法
  4. 反向运算用__radd__等——当左操作数不支持时被调用
  5. 不支持的操作返回NotImplemented——让Python尝试对方的反向方法

原则:运算符的行为应该"符合直觉"。+永远是加法或拼接,==永远是比较相等——不要搞创新。

觉得上面的内容有用吗?快来点个赞吧!

点赞() 我要打赏

温馨提示 : 本站内容来自会员投稿以及互联网,所有源码及教程均为作者总结编辑,请大家在使用过程中提前做好备份,以免发生无法预知的错误,源码类教程请勿直接用于生产环境!

 可能感兴趣的文章