如何在更改对象属性后保留用户定义对象集中元素的唯一性

How to retain uniqueness of elements inside Set of user-defined objects after changing attribute of object

如何保留 Set 的唯一性特性,以便在将自定义实例添加到 Set 后修改属性?

就像下面的代码一样: Person "Jack" 和 "John" 在相等性 "Name" 方面是不同的。所以他们都被添加到集合中 但是如果我将人名“Jack”更改为“John”,那么 2 个实例 jack 和 john 将相等 但是我的 Set 没有反映出这一点。他们仍然认为这两个实例是不同的

注意:当有人在将用户定义的实例添加到集合中后意外修改它们时,这会导致潜在的错误

我们有办法刷新集合吗?我该如何避免这个问题?

class Person: 
    def __init__(self, name): 
        self.name = name 
    def __eq__(self, other):
        return self.name == other.name
    def __hash__(self):
        return hash(self.name)
jack = Person("Jack")
john = Person("John")
set1 = {jack, john}
jack.name = "John"
print(set1) // return 2 instance instead of 1. This is undesired behavior because now both jack & john are equal 

您创建了两个不同的对象,如果您打印 set1,您将得到如下内容:{<__main__.Person object at 0x7f8dfbfc5e10>, <__main__.Person object at 0x7f8dfbfe2a10>}

虽然它们的属性名称不同,但它们仍然是两个不同的对象,保存在不同的内存空间中。这就是为什么当你把它们放在一个集合中时,你会有意想不到的行为,仍然有它们!

当您执行 jack.name = "John" 时,您只是在更改属性 self.name

为了得到你想要的结果你必须做:set1 = {jack.name, john.name}

它会return你{'John'}

您应该只使用 set 的不可变对象或引用。见 Python docs:

Having a __hash__() implies that instances of the class are immutable.

set 中的 Person 对象是可变的,但您已经实现了自己的散列函数和相等函数来解决这个问题,正如您所指出的那样,绕过了安全性。

我认为定义自定义散列函数和相等函数很好,但它们应该始终 return 相同,无论您对它们引用的内容做什么:例如,使用 ID 或内存地址进行散列。

我建议从两个选项中选择一个,强烈推荐第一个:

选项 A:不可变 Person

构造时使 Person 不可变。我最喜欢的方法是使用数据类:

from dataclasses import dataclass

@dataclass(frozen=True)
class Person:
    name: str

jack = Person("Jack")
john = Person("John")

# Note you don't need to define your own hash method.

set1 = {jack, john}

# This will fail:

jack.name = "Jaques"

# Consider the need for this. But if you have, say, a lot of different
# fields on the Person and want to just change one or a few, try:

import dataclasses

jaques = dataclasses.replace(jack, {"name": "Jaques"})

# But note this is a different object. The set is still the same as before.
# You need to remove "jack" from the set and add "jaques" to it.

选项 B:重新计算集合

请注意,我认为这不是个好主意,但您可以简单地 运行:

set1 = {jack, john}

...再一次,它会重新计算集合。