鉴于此计划:
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'
Run Code Online (Sandbox Code Playgroud)
如果我使用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>
Run Code Online (Sandbox Code Playgroud)
这是Python中的错误吗?或者关于套件实现的一些我不知道的事情?
有趣的是,它似乎总是objs.remove()在Python 2.7.9 的第二次调用中失败.
这不是Python中的错误,您的代码违反了集合原则:散列值不得更改.通过改变对象属性,散列更改和集合无法再可靠地定位集合中的对象.
如果一个类定义了可变对象并实现了一个
__eq__()方法,那么它就不应该实现__hash__(),因为hashable集合的实现要求一个键的哈希值是不可变的(如果对象的哈希值改变,它将在错误的哈希桶中).
自定义Python类定义一个默认__eq__方法,当两个操作数引用同一个对象时返回True(obj1 is obj2为true).
它有时在Python 3中有效是字符串的哈希随机化的属性.因为字符串的哈希值在Python解释器运行之间发生变化,并且因为使用了散列的模数与散列表的大小,所以无论如何都可以最终得到正确的散列槽,纯粹是偶然的,然后是==等式测试仍然是真的,因为你没有实现自定义__eq__方法.
Python 2也有哈希随机化,但默认情况下它被禁用,但你可以通过仔细选择a和b属性的'正确'值来使你的测试'通过' .
相反,您可以通过基于id()实例的哈希来使代码工作; 这使得哈希值不会改变,并且与默认__eq__实现相匹配:
def __hash__(self):
return hash(id(self))
Run Code Online (Sandbox Code Playgroud)
您也可以删除您的__hash__实现以获得相同的效果,因为默认实现基本上完成上述操作(将id()值旋转4位以避开内存对齐模式).再次,从__hash__文档:
用户定义的类默认具有
__eq__()和__hash__()方法; 与它们相比,所有对象都比较不相等(除了它们自己)并x.__hash__()返回一个适当的值,这x == y意味着它x is y和hash(x) == hash(y).
或者,实现一种__eq__方法,该方法基于实例属性的相等性,并且不会改变属性.
| 归档时间: |
|
| 查看次数: |
66 次 |
| 最近记录: |