ZetCode

Python __eq__ 方法

最后修改于 2025 年 4 月 8 日

本综合指南探讨了 Python 的 __eq__ 方法,该特殊方法负责相等性比较。我们将涵盖基本用法、自定义比较、哈希一致性、继承和实际示例。

基本定义

__eq__ 方法定义了相等运算符 (==) 的行为。如果对象相等,它应该返回 True,否则返回 False,如果不支持比较,则返回 NotImplemented

关键特征:它接受两个参数(self 和 other),应该实现自反性、对称性和传递性,并且通常与 __hash__ 结合使用,以实现集合中的一致行为。

基本的 __eq__ 实现

这是一个简单的实现,展示了 __eq__ 如何比较对象。我们将创建一个 Point 类来比较坐标。

basic_eq.py
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __eq__(self, other):
        if not isinstance(other, Point):
            return NotImplemented
        return self.x == other.x and self.y == other.y

p1 = Point(1, 2)
p2 = Point(1, 2)
p3 = Point(3, 4)
print(p1 == p2)  # True
print(p1 == p3)  # False
print(p1 == "not a point")  # False (uses NotImplemented)

此示例显示了 Point 实例之间的基本相等性比较。 __eq__ 方法检查另一个对象是否为 Point 并比较坐标。对于非 Point 对象,它返回 NotImplemented

NotImplemented 返回值允许 Python 尝试反向操作或回退到默认行为。这保持了正确的比较语义。

使用业务逻辑的自定义相等性

__eq__ 可以实现特定于业务的相等性规则。在这里,我们按帐号比较 BankAccount 对象,忽略余额差异。

business_eq.py
class BankAccount:
    def __init__(self, account_number, balance):
        self.account_number = account_number
        self.balance = balance
    
    def __eq__(self, other):
        if not isinstance(other, BankAccount):
            return NotImplemented
        return self.account_number == other.account_number
    
    def __repr__(self):
        return f"BankAccount({self.account_number}, ${self.balance})"

acc1 = BankAccount("12345", 1000)
acc2 = BankAccount("12345", 500)
acc3 = BankAccount("67890", 1000)
print(acc1 == acc2)  # True (same account number)
print(acc1 == acc3)  # False

此实现认为如果帐户具有相同的帐号,则它们相等,而与余额无关。这对于检查系统中帐户是否存在可能很有用。

请注意我们仍然检查 other 的类型以保持比较安全。 __repr__ 方法有助于调试,但不影响相等性比较。

带继承的相等性

在处理继承时,__eq__ 需要仔细实现,以保持父类和子类之间正确的比较语义。

inheritance_eq.py
class Vehicle:
    def __init__(self, make, model):
        self.make = make
        self.model = model
    
    def __eq__(self, other):
        if not isinstance(other, Vehicle):
            return NotImplemented
        return self.make == other.make and self.model == other.model

class Car(Vehicle):
    def __init__(self, make, model, doors):
        super().__init__(make, model)
        self.doors = doors
    
    def __eq__(self, other):
        if not isinstance(other, Car):
            return NotImplemented
        return super().__eq__(other) and self.doors == other.doors

v1 = Vehicle("Toyota", "Camry")
c1 = Car("Toyota", "Camry", 4)
c2 = Car("Toyota", "Camry", 2)
print(v1 == c1)  # False (different types)
print(c1 == c2)  # False (different doors)

此示例显示了继承层次结构中正确的相等性处理。 Car 类使用其自己的属性比较扩展了 Vehicle 的相等性检查。

请注意,即使 Vehicle 和 Car 实例共享制造商和型号,它们也永远不相等。 这通过不将不同类型视为相等来维持 Liskov 替换原则。

保持哈希一致性

在重写 __eq__ 时,您还应该考虑 __hash__ 以保持字典和集合中的正确行为。 这是具体做法。

hash_eq.py
class Product:
    def __init__(self, id, name, price):
        self.id = id
        self.name = name
        self.price = price
    
    def __eq__(self, other):
        if not isinstance(other, Product):
            return NotImplemented
        return self.id == other.id
    
    def __hash__(self):
        return hash(self.id)
    
    def __repr__(self):
        return f"Product({self.id}, '{self.name}', {self.price})"

p1 = Product(1, "Laptop", 999)
p2 = Product(1, "Laptop Pro", 1299)
products = {p1, p2}
print(products)  # Only contains one product (same ID)

此示例显示了如何在重写相等性时保持哈希一致性。 比较相等的对象必须具有相同的哈希值才能在基于哈希的集合中正常工作。

我们使用产品 ID 进行相等性比较和哈希处理,从而确保一致的行为。 在此实现中,名称和价格更改不会影响相等性或哈希处理。

不区分大小写的字符串比较

__eq__ 可以实现替代的比较逻辑,例如自定义字符串类中的不区分大小写的字符串比较。

case_insensitive_eq.py
class CaseInsensitiveString:
    def __init__(self, value):
        self.value = value
    
    def __eq__(self, other):
        if isinstance(other, str):
            return self.value.lower() == other.lower()
        if not isinstance(other, CaseInsensitiveString):
            return NotImplemented
        return self.value.lower() == other.value.lower()
    
    def __str__(self):
        return self.value

s1 = CaseInsensitiveString("Hello")
s2 = CaseInsensitiveString("hello")
print(s1 == s2)  # True
print(s1 == "HELLO")  # True
print("WORLD" == CaseInsensitiveString("world"))  # True

此类使用其自身的实例和常规字符串实现不区分大小写的比较。 __eq__ 方法通过在比较之前将字符串转换为小写来处理这两种情况。

请注意,它是如何在两个方向(a == b 和 b == a)上工作的,因为我们直接处理字符串比较,并为其他类型返回 NotImplemented,从而允许 Python 尝试反向操作。

最佳实践

资料来源

作者

我的名字是 Jan Bodnar,我是一位热情的程序员,拥有丰富的编程经验。 自 2007 年以来,我一直在撰写编程文章。 迄今为止,我已经撰写了超过 1,400 篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有 Python 教程