我认为解释这种情况的最好方法是举个例子:
>>> class Person:
... def __init__(self, brother=None):
... self.brother = brother
...
>>> bob = Person()
>>> alice = Person(brother=bob)
>>> import shelve
>>> db = shelve.open('main.db', writeback=True)
>>> db['bob'] = bob
>>> db['alice'] = alice
>>> db['bob'] is db['alice'].brother
True
>>> db['bob'] == db['alice'].brother
True
>>> db.close()
>>> db = shelve.open('main.db',writeback=True)
>>> db['bob'] is db['alice'].brother
False
>>> db['bob'] == db['alice'].brother
False
两次比较的预期输出再次为True
。但是,pickle
(由shelve
使用)似乎是分别重新实例化bob
和alice.brother
。如何使用shelve
/ pickle
“修复”此问题? db['alice'].brother
是否有可能指向db['bob']
或类似的东西?请注意,我不想只比较两者,我需要两者实际上是相同的。
正如 Blckknght 所建议的那样,我尝试一次腌制整个字典,但问题仍然存在,因为它似乎分别腌制每个密钥。
答案 0 :(得分:2)
我相信您看到的问题来自shelve
模块存储其值的方式。每个值都独立于工具架中的其他值进行pickle,这意味着如果在多个键下插入相同的对象作为值,则不会在键之间保留标识。但是,如果单个值具有对同一对象的多个引用,则该标识将保持在该单个值内。
以下是一个例子:
a = object() # an arbitrary object
db = shelve.open("text.db")
db['a'] = a
db['another_a'] = a
db['two_a_references'] = [a, a]
db.close()
db = shelve.open("text.db") # reopen the db
print(db['a'] is db['another_a']) # prints False
print(db['two_a_references'][0] is db['two_a_references'][1]) # prints True
第一次打印尝试确认插入数据库的对象a
的两个版本的标识,一个直接在密钥'a'
下,另一个在'another_a'
下。它不起作用,因为单独的值被单独腌制,因此它们之间的身份丢失了。
第二次打印测试是否保留了存储在密钥a
下的'two_a_references'
的两个引用。因为列表一次性被腌制,所以保留了身份。
为了解决您的问题,您有几个选择。一种方法是避免测试身份并依赖各种对象类型中的__eq__
方法来确定两个对象在语义上是否相等,即使它们不是同一个对象。另一种方法是将您的所有数据捆绑到单个对象(例如字典)中,然后使用pickle.dump
保存并使用pickle.load
进行恢复,而不是使用shelve
(或者您可以调整this recipe for a persistent dictionary,它是从shelve
文档链接的,并且几乎完全相同。
答案 1 :(得分:1)
在Python中,适当的方法是在Person类中实现__eq__
和__ne__
函数,如下所示:
class Person(object):
def __eq__(self, other):
return (isinstance(other, self.__class__)
and self.__dict__ == other.__dict__)
def __ne__(self, other):
return not self.__eq__(other)
通常,这应该足够了,但如果它们是真正的数据库对象并且具有主键,那么检查该属性而不是self.__dict__
会更有效。
答案 2 :(得分:1)
<强>问题强>
要保留shelve
的身份,您需要使用pickle
read this保留身份。
<强>解决方案强>
此类保存其类站点上的所有对象,并在标识相同时恢复它们。你应该可以从中继承。
>>> class PickleWithIdentity(object):
identity = None
identities = dict() # maybe use weakreference dict here
def __reduce__(self):
if self.identity is None:
self.identity = os.urandom(10) # do not use id() because it is only 4 bytes and not random
self.identities[self.identity] = self
return open_with_identity, (self.__class__, self.__dict__), self.__dict__
>>> def open_with_identity(cls, dict):
if dict['identity'] in cls.identities:
return cls.identities[dict['identity']]
return cls()
>>> p = PickleWithIdentity()
>>> p.asd = 'asd'
>>> import pickle
>>> import os
>>> pickle.loads(pickle.dumps(p))
<__main__.PickleWithIdentity object at 0x02D2E870>
>>> pickle.loads(pickle.dumps(p)) is p
True
可能会出现更多问题,因为状态可能会被覆盖:
>>> p.asd
'asd'
>>> ps = pickle.dumps(p)
>>> p.asd = 123
>>> pickle.loads(ps)
<__main__.PickleWithIdentity object at 0x02D2E870>
>>> p.asd
'asd'