【问题标题】:Why is my hashable object not found in a set of that hashable object, the set being an attribute of another object?为什么在该可散列对象的集合中找不到我的可散列对象,该集合是另一个对象的属性?
【发布时间】:2018-09-02 05:01:53
【问题描述】:

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

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

一个对象是hashable,如果它的哈希值在其生命周期内永远不会改变(它需要一个__hash__() 方法),并且可以与其他对象进行比较(它需要一个__eq__() 方法)。

我真的不知道我下面的 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。

【问题讨论】:

  • 一般来说:一个可散列对象应该是不可变的,或者至少它的散列应该是稳定的并且不依赖于可变属性。您的对象的哈希值会随着时间而变化,这不是 Good™️。
  • 对我来说,add 电话有效。为什么它不工作?
  • 啊,我的错。看,太混乱了! ;)
  • 答案可能在于可变散列如何违背set 内部所做的假设。因此,问题可能是您的哈希值发生了变异。
  • @Erik:坦率地说,你可以大大减少你的代码。并且不需要类型提示,所有这些都适用于非 python 3.6 用户。我会放弃最统一的部分。只需打印结果...

标签: python list hash set hashable


【解决方案1】:

问题是您的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,正如您所期望的那样。没有理由不这样做。我怀疑调试时出现了某种错误.)


使单元测试工作的一个简单方法是交换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. 寻找一致的哈希方法

【讨论】:

  • 解释了第一个失败的断言。但不是bar in {bar}(不要试图解释,因为它对我有用:))
  • 谢谢。这个答案和我问题上的 cmets 让我得出结论,对于我的用例,我需要删除循环依赖。
  • @Jean-FrançoisFabre 我刚刚意识到整个bar in {bar} 甚至在问题标题中......所以我想我应该在我的答案中解决它或编辑它,因为它真的这些类无法重现。
  • 但没有什么可解决的:它有效。 OP 应该编辑它。
猜你喜欢
  • 2012-01-10
  • 1970-01-01
  • 1970-01-01
  • 2010-12-29
  • 2011-12-22
  • 2013-06-22
  • 2019-08-26
  • 1970-01-01
  • 2011-09-12
相关资源
最近更新 更多