python - 从 Python 集合中删除已更改的对象

标签 python python-3.x

给定这个程序:

class Obj:
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def __hash__(self):
        return hash((self.a, self.b))

class Collection:
    def __init__(self):
        self.objs = set()

    def add(self, obj):
        self.objs.add(obj)

    def find(self, a, b):
        objs = []

        for obj in self.objs:
            if obj.b == b and obj.a == a:
                objs.append(obj)
        return objs

    def remove(self, a, b):
        for obj in self.find(a, b):
            print('removing', obj)
            self.objs.remove(obj)

o1 = Obj('a1', 'b1')
o2 = Obj('a2', 'b2')
o3 = Obj('a3', 'b3')
o4 = Obj('a4', 'b4')
o5 = Obj('a5', 'b5')

objs = Collection()
for o in (o1, o2, o3, o4, o5):
    objs.add(o)

objs.remove('a1', 'b1')
o2.a = 'a1'
o2.b = 'b1'
objs.remove('a1', 'b1')
o3.a = 'a1'
o3.b = 'b1'
objs.remove('a1', 'b1')
o4.a = 'a1'
o4.b = 'b1'
objs.remove('a1', 'b1')
o5.a = 'a1'
o5.b = 'b1'

如果我用 Python 3.4.2 运行几次,有时它会成功,有时它会在删除 2 或 3 个对象后抛出 KeyError:

$ python3 py_set_obj_remove_test.py
removing <__main__.Obj object at 0x7f3648035828>
removing <__main__.Obj object at 0x7f3648035860>
removing <__main__.Obj object at 0x7f3648035898>
removing <__main__.Obj object at 0x7f36480358d0>

$ python3 py_set_obj_remove_test.py
removing <__main__.Obj object at 0x7f156170b828>
removing <__main__.Obj object at 0x7f156170b860>
Traceback (most recent call last):
  File "py_set_obj_remove_test.py", line 42, in <module>
    objs.remove('a1', 'b1')
  File "py_set_obj_remove_test.py", line 27, in remove
    self.objs.remove(obj)
KeyError: <__main__.Obj object at 0x7f156170b860>

这是 Python 中的错误吗?或者关于我不知道的集合的实现?

有趣的是,它似乎总是在 Python 2.7.9 中的第二次 objs.remove() 调用时失败。

最佳答案

这不是 Python 中的错误,您的代码违反了集合原则:哈希值不得更改。通过改变您的对象属性,散列会发生变化,并且集合无法再可靠地在集合中定位对象。

来自 __hash__ method documentation :

If a class defines mutable objects and implements an __eq__() method, it should not implement __hash__(), since the implementation of hashable collections requires that a key’s hash value is immutable (if the object’s hash value changes, it will be in the wrong hash bucket).

自定义 Python 类定义默认 __eq__当两个操作数引用同一对象时返回 True 的方法(obj1 is obj2 为真)。

有时 在 Python 3 中工作是 hash randomisation 的一个属性对于字符串。因为字符串的散列值在 Python 解释器运行之间会发生变化,并且因为使用了散列对散列表大小的模数,所以您可以无论如何最终得到正确的散列槽,纯粹是事故,然后是==相等性测试仍然为真,因为您没有实现自定义 __eq__方法。

Python 2 也有散列随机化,但默认情况下它是禁用的,但是您可以通过仔细为 a 选择“正确”值来使您的测试“通过”。和 b属性。

相反,您可以通过将哈希值基于 id() 来使您的代码正常工作你的例子;这使得哈希值不会改变并且会匹配默认值 __eq__实现:

def __hash__(self):
    return hash(id(self))

您也可以删除您的 __hash__相同效果的实现,因为默认实现基本上执行上述操作(将 id() 值旋转 4 位以避开内存对齐模式)。同样,来自 __hash__文档:

User-defined classes have __eq__() and __hash__() methods by default; with them, all objects compare unequal (except with themselves) and x.__hash__() returns an appropriate value such that x == y implies both that x is y and hash(x) == hash(y).

或者,实现一个 __eq__方法基于实例属性的相等性,并且不改变属性

关于python - 从 Python 集合中删除已更改的对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38746185/

相关文章:

python - 十进制日期和时间转换

python - Numpy 矩阵索引等价

python - 从远程目录导入 Python 模块

python - 使用 QSqlTableModel 和 QTableView 模型和 View 布局,是否可以在我的表中有一列来隐藏行

python - 根据分组和条件更新数据框列

python-3.x - AWS Lambda Python 3.7 网页抓取 - "Could not get version for Chrome with this command: google-chrome --version"

python - 使用 Python 填写 Web 表单

python - Pandas:动态计算流失发生的位置

python - 使用GPIO通过按钮控制树莓派picamera

Python 3 错误 : ValueError: invalid literal for int() with base 10: ''