为什么在该可哈希对象的集合中找不到我的可哈希对象,该集合是另一个对象的属性?
Why is my hashable object not found in a set of that hashable object, the set being an attribute of another object?
我在两个 类 的对象之间有一个递归关系:Foo
有一个 set
个 Bar
对象(在它的 bars
属性中)和每个 Bar
都有 list
个 Foo
对象(在其 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,Python3.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
。没有理由不这样做。我怀疑有调试时出现某种错误。)
使单元测试工作的一个简单方法是交换 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
,您将再次遇到同样的问题。
我认为很明显,相互依赖 类 且散列不一致是一个糟糕的设计选择。可能的解决方案包括
- 删除
Foo
-> Bar
-> Foo
依赖循环
- 寻找一致的散列方法
我在两个 类 的对象之间有一个递归关系:Foo
有一个 set
个 Bar
对象(在它的 bars
属性中)和每个 Bar
都有 list
个 Foo
对象(在其 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,Python3.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
。没有理由不这样做。我怀疑有调试时出现某种错误。)
使单元测试工作的一个简单方法是交换 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
,您将再次遇到同样的问题。
我认为很明显,相互依赖 类 且散列不一致是一个糟糕的设计选择。可能的解决方案包括
- 删除
Foo
->Bar
->Foo
依赖循环 - 寻找一致的散列方法