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

问题描述

如何在将自定义实例添加到Set后,保留Set的Uniqueness特性来修改自定义实例的属性

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

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

我们是否有办法刷新 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 

解决方法

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

拥有 __hash__() 意味着类的实例是不可变的。

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

我认为定义自定义散列和相等函数是可以的,但无论您对它们引用的内容做什么,它们都应该始终返回相同的内容:例如,使用 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}

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

,

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

即使它们的属性名称不同,它们仍然是保存在不同内存空间中的两个不同对象。这就是为什么当你将它们放入一个集合时,你仍然拥有它们的意外行为!

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

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

它会回报你{'John'}