Python“魔术方法”详解:self 与 other 的角色与交互
本文深入解析了 Python 中的“特殊方法”(也称“魔术方法”),特别是以 __eq__ 为代表的、使用 (self, other) 参数对的模式。文章的核心在于阐明 self(操作符左侧对象)和 other(右侧对象)的含义,以及如何通过实现这些方法,让你自定义的类能够响应 Python 的内置运算符。
内容系统地分类并详解了:
-
丰富比较方法 (如
__eq__,__gt__):用于实现==,>等比较。 -
二元算术方法 (如
__add__,__mul__):用于实现+,*等运算。 -
反向算术方法 (如
__radd__,__rmul__):用于处理当other在self左侧时的运算(例如2 * my_object)。 -
增强赋值方法 (如
__iadd__):用于实现+=等原地修改操作。 -
其他相关模式 (如
__contains__):用于实现in操作符。
通过 Person、Vector 等类别的代码示例,清晰地展示了这些方法在实践中的调用逻辑和 NotImplemented 返回值的重要作用。
1. 丰富比较运算符 (Rich Comparison Operators)
-
__eq__(self, other)-
操作符:
self == other -
详解: 检查
self是否等于other。
-
-
__ne__(self, other)-
操作符:
self != other -
详解: 检查
self是否不等于other。 (如果你没定义__ne__,Python 会默认调用__eq__并取反)。
-
-
__lt__(self, other)-
操作符:
self < other -
详解: 检查
self是否小于other。
-
-
__le__(self, other)-
操作符:
self <= other -
详解: 检查
self是否小于等于other。
-
-
__gt__(self, other)-
操作符:
self > other -
详解: 检查
self是否大于other。
-
-
__ge__(self, other)-
操作符:
self >= other -
详解: 检查
self是否大于等于other。
-
重要提示:NotImplemented 在实现这些方法时,如果 other 是一个你的类无法比较的类型,你应该 return NotImplemented(而不是 False)。这会给 Python 一个机会去尝试调用 other 对象的反向比较方法(例如,a == b 失败后,Python 会尝试 b == a)。
class Person:def __init__(self, name, age):self.name = nameself.age = age# 详解 __eq__def __eq__(self, other):print(f"调用 {self.name}.__eq__({other})")# 检查 other 是不是 Person 类型if isinstance(other, Person):# 如果是,我们定义“相等”为 name 和 age 都相等return self.name == other.name and self.age == other.age# 如果类型不同,我们不知道怎么比,返回 NotImplementedreturn NotImplemented# 详解 __gt__ (大于)def __gt__(self, other):print(f"调用 {self.name}.__gt__({other})")if isinstance(other, Person):# 我们定义“大于”为年龄更大return self.age > other.agereturn NotImplementeddef __repr__(self):# 方便打印时查看return f"Person('{self.name}', {self.age})"p1 = Person("Alice", 25)
p2 = Person("Alice", 25)
p3 = Person("Bob", 30)# 示例 1: == 操作
# 这会调用 p1.__eq__(p2)
# self 是 p1, other 是 p2
print(f"p1 == p2: {p1 == p2}\n")
# 输出:
# 调用 Alice.__eq__(Person('Alice', 25))
# p1 == p2: True# 示例 2: > 操作
# 这会调用 p3.__gt__(p1)
# self 是 p3, other 是 p1
print(f"p3 > p1: {p3 > p1}\n")
# 输出:
# 调用 Bob.__gt__(Person('Alice', 25))
# p3 > p1: True# 示例 3: 和不同类型比较
# 这会调用 p1.__eq__(100)
# self 是 p1, other 是 100
print(f"p1 == 100: {p1 == 100}\n")
# 输出:
# 调用 Alice.__eq__(100)
# p1 == 100: False
# (因为 __eq__ 返回了 NotImplemented,Python 尝试 100.__eq__(p1) 也失败,最终结果为 False)
2. 二元算术运算符 (Binary Arithmetic Operators)
这些方法用于实现 +, -, *, / 等算术运算。
-
__add__(self, other)-
操作符:
self + other
-
-
__sub__(self, other)-
操作符:
self - other
-
-
__mul__(self, other)-
操作符:
self * other
-
-
__truediv__(self, other)-
操作符:
self / other(真除法,例如5 / 2 = 2.5)
-
-
__floordiv__(self, other)-
操作符:
self // other(地板除,例如5 // 2 = 2)
-
-
__mod__(self, other)-
操作符:
self % other(取模)
-
-
__pow__(self, other)-
操作符:
self ** other(幂运算)
-
-
__lshift__(self, other)-
操作符:
self << other(按位左移)
-
-
__rshift__(self, other)-
操作符:
self >> other(按位右移)
-
-
__and__(self, other)-
操作F符:
self & other(按位与)
-
-
__or__(self, other)-
操作符:
self | other(按位或)
-
-
__xor__(self, other)-
操作符:
self ^ other(按位异或)
-
class Vector:def __init__(self, x, y):self.x = xself.y = y# 详解 __add__def __add__(self, other):print(f"调用 Vector({self.x}, {self.y}).__add__({other})")if isinstance(other, Vector):# 向量相加:(x1+x2, y1+y2)return Vector(self.x + other.x, self.y + other.y)return NotImplementeddef __repr__(self):return f"Vector({self.x}, {self.y})"v1 = Vector(1, 2)
v2 = Vector(10, 20)# 这会调用 v1.__add__(v2)
# self 是 v1, other 是 v2
result = v1 + v2
print(f"结果: {result}")
# 输出:
# 调用 Vector(1, 2).__add__(Vector(10, 20))
# 结果: Vector(11, 22)
3. 反向算术运算符 (Reflected Arithmetic Operators)
这是 self 和 other 概念中一个非常重要的补充。
场景:当你执行 other + self 时,Python 会首先尝试调用 other.__add__(self)。如果 other(比如是一个 int)不知道如何与你的 Vector 对象相加(即 int.__add__ 失败或返回 NotImplemented),Python 就会反过来,尝试调用 self 的反向方法。
反向方法就是在原方法名前加一个 r。
-
__radd__(self, other)-
触发:
other + self(当other.__add__(self)失败时) -
注意: 此时
self仍然是你的对象(在+右侧),other是左侧的对象。
-
-
__rsub__(self, other)-
触发:
other - self
-
-
__rmul__(self, other)-
触发:
other * self
-
-
... (其他算术运算符都有对应的
r版本)
示例(续上例):
class Vector:# ... (接上例的 __init__, __add__, __repr__) ...# 让我们允许向量和一个数字相乘def __mul__(self, other):print(f"调用 {self}.__mul__({other})")if isinstance(other, (int, float)):# 向量 * 2return Vector(self.x * other, self.y * other)return NotImplemented# 详解 __rmul__def __rmul__(self, other):# 当 2 * v1 时, Python 尝试 int.__mul__(v1),失败。# 于是 Python 调用 v1.__rmul__(2)# 此时 self 是 v1, other 是 2print(f"调用 {self}.__rmul__({other})")# 逻辑通常和 __mul__ 一样return self.__mul__(other)v1 = Vector(1, 2)# 示例 1: v1 * 2
# 调用 v1.__mul__(2)
print(f"v1 * 2 = {v1 * 2}\n")
# 输出:
# 调用 Vector(1, 2).__mul__(2)
# v1 * 2 = Vector(2, 4)# 示例 2: 2 * v1
# 调用 2.__mul__(v1) -> 失败 (int 不知道怎么乘 Vector)
# 于是 Python 转而调用 v1.__rmul__(2)
print(f"2 * v1 = {2 * v1}\n")
# 输出:
# 调用 Vector(1, 2).__rmul__(2)
# 调用 Vector(1, 2).__mul__(2) (在 __rmul__ 内部又调用了 __mul__)
# 2 * v1 = Vector(2, 4)
4. 增强赋值运算符 (In-place Assignment Operators)
这些用于 +=, -=, *= 等操作。
-
__iadd__(self, other)-
操作符:
self += other
-
-
__isub__(self, other)-
操作符:
self -= other
-
-
__imul__(self, other)-
操作F符:
self *= other
-
-
... (其他算术运算符都有对应的
i版本)
关键点: 这类方法应该(如果可能)直接修改 self,并且必须返回 self。如果没定义 __iadd__,Python 会退而求其次,执行 self = self + other (即调用 __add__)。
class NumberBox:def __init__(self, value):self.value = value# 详解 __iadd__def __iadd__(self, other):print(f"调用 {self}.__iadd__({other})")if isinstance(other, (int, float)):# 直接修改 self.valueself.value += other# 必须返回 selfreturn self return NotImplementeddef __repr__(self):return f"NumberBox({self.value})"box = NumberBox(10)
print(f"box 的 ID (操作前): {id(box)}")# 这会调用 box.__iadd__(5)
box += 5 print(f"box 的 ID (操作后): {id(box)}") # ID 没变
print(f"box 的新值: {box}")
# 输出:
# box 的 ID (操作前): ...
# 调用 NumberBox(10).__iadd__(5)
# box 的 ID (操作后): ... (和上面一样)
# box 的新值: NumberBox(15)
5. 其他 (self, other) 模式的方法
还有一些方法虽然参数名不叫 other,但模式是完全一样的 (self, item) 或 (self, key)。
-
__contains__(self, item)-
操作符:
item in self -
详解: 检查
item是否包含在self容器中。self是容器,item是被检查的对象。
-
-
__getitem__(self, key)-
操作符:
self[key] -
详解: 获取
self中key对应的值。
-
-
__setitem__(self, key, value)-
操作符:
self[key] = value -
详解: 设置
self中key对应的值为value。(这是(self, other, another)的模式)
-
-
__delitem__(self, key)-
操作符:
del self[key] -
详解: 删除
self中key对应的值。
-
示例 __contains__:
class Team:def __init__(self, members):self.members = set(members)# 详解 __contains__def __contains__(self, member_name):# self 是 my_team, member_name 就是 "Bob"print(f"调用 Team.__contains__({member_name})")return member_name in self.membersmy_team = Team(["Alice", "Bob", "Charlie"])# 这会调用 my_team.__contains__("Bob")
print(f"'Bob' in my_team: {"Bob" in my_team}")
# 输出:
# 调用 Team.__contains__(Bob)
# 'Bob' in my_team: True
总结
self 和 other 是 Python 运算符重载的核心。
-
self是操作符左侧的对象。 -
other是操作符右侧的对象。 -
比较方法(
__eq__等)用于==,<等。 -
算术方法(
__add__等)用于+,*等。 -
反向方法(
__radd__等)用于当左侧对象(other)无法处理该操作时,Python 会反转操作,让self(右侧对象)来尝试处理。 -
原地修改方法(
__iadd__等)用于+=,应直接修改self并返回self。 -
NotImplemented是一个特殊的返回值,用于告诉 Python “我不知道怎么处理这个类型,你试试别的办法吧”(比如尝试反向操作)。
