python - 为什么在该可哈希对象的集合中找不到我的可哈希对象,该集合是另一个对象的属性?

标签 python list hash set hashable

我在两个类的对象之间有一个递归关系:Foo有一组Bar对象(在它的bars 属性),每个 Bar 都有一个 Foo 对象的 list(在其 foos 属性中)。我已经实现了如下面的MWE所示,但测试失败。为什么?

根据Python Glossary ,一个set只能包含hashable对象和hashable对象:

An object is hashable if it has a hash value which never changes during its lifetime (it needs a __hash__() method), and can be compared to other objects (it needs an __eq__() method).

我真的不知道下面的 MWE 是否满足对象具有永不更改的哈希,因为哈希取决于列表中的其他对象并设置属性。有办法解决这个问题吗?

最小工作示例:

import unittest


class Test(unittest.TestCase):
    def test_foo_bar(self):
        foo = Foo()
        bar = Bar()
        bar.add_foo(foo)

        print(bar)
        print(foo.bars)
        print(hash(bar))
        for bar in foo.bars:
            print(hash(bar))
        # The previous print lines print the following:
        # <mwe2.Bar object at 0x105ba8080>
        # {<mwe2.Bar object at 0x105ba8080>}
        # -9223096319794529578
        # -9223096319794529578

        # The following assertion is OK
        self.assertTrue(bar in {bar})

        # The following assertion fails with AssertionError: False is not true
        self.assertTrue(bar in foo.bars)



class Foo:
    def __init__(self):
        self.bars = set()

    def __hash__(self) -> int:
        return hash(self.__dict__.values())


class Bar:
    def __init__(self):
        self.foos = list()

    def __hash__(self) -> int:
        return hash(tuple(self.foos))

    def add_foo(self, foo: "Foo"):
        foo.bars.add(self)
        self.foos.append(foo)


if __name__ == '__main__':
    unittest.main()

我使用 CPython、Python 3.6.x。

最佳答案

问题在于,您的 Bar 的哈希值在您将其添加到 Foo.bars 后立即发生了变化。如果您在 add_foo 方法中添加一些 print 语句,您可以看到这一点:

def add_foo(self, foo: "Foo"):
    foo.bars.add(self)
    print(hash(self))
    self.foos.append(foo)
    print(hash(self))

输出:

3527539
957074234

这是因为哈希值是根据 self.foos 计算的,因此对 self.foos 的任何修改也会更改对象的哈希值。

(旁注:与问题中所述相反,bar in {bar} 的计算结果为 True 正如您所期望的。没有理由它不会t。我怀疑调试时出现了某种错误。)

<小时/>

使单元测试工作的一个简单方法是交换 add_foo 中的两行代码:

def add_foo(self, foo: "Foo"):
    self.foos.append(foo)  # append first
    foo.bars.add(self)  # add to the set with the new hash

输出:

Ran 1 test in 0.000s

OK

但是,这并不是一个真正的修复:如果您多次调用 add_foo ,这将无济于事。如果您在将 Bar 对象添加到集合或字典后调用 add_foo,您将再次遇到同样的问题。

我认为很明显,具有不一致哈希值的相互依赖的类是一个糟糕的设计选择。可能的解决方案包括

  1. 删除 Foo -> Bar -> Foo 依赖循环
  2. 寻找一致的哈希方法

关于python - 为什么在该可哈希对象的集合中找不到我的可哈希对象,该集合是另一个对象的属性?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49451001/

相关文章:

ruby - Ruby 中的数组或哈希

ruby - 哈希方法使用什么相等性?

python - ReportLab 中的非编号页面

python - 用 pandas 转换格式不良的字典

python - 在没有 Pandas 的情况下对多列进行分组和求和

Python3 Crypto.Hash - SHA 摘要总是以二进制 1 开头

python - 如何将 C++ for 循环转换为 Python?

python - 如何在 Python 2.6 中使用 Numerical Python

python - 从 zip 复制文件并同时读取该文件

java - 如何从java列表中删除具有相同值的对象