存储在工具架中的Python实例在关闭后会发生变化

存储在工具架中的Python实例在关闭后会发生变化,python,instance,pickle,shelve,Python,Instance,Pickle,Shelve,我认为最好用一个例子来解释这种情况: >>> class Person: ... def __init__(self, brother=None): ... self.brother = brother ... >>> bob = Person() >>> alice = Person(brother=bob) >>> import shelve >>> db = shelve.o

我认为最好用一个例子来解释这种情况:

>>> 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
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
两个比较的预期输出再次为真。然而,shelve使用的pickle似乎分别重新实例化了bob和alice.brother。我怎样才能用架子/泡菜来解决这个问题?db['alice'].brother是否可以指向db['bob']或类似的东西?注意,我不想只比较两者,我需要两者实际上是相同的


正如Blckknght所建议的那样,我尝试一次对整个字典进行pickle,但问题仍然存在,因为它似乎是分别对每个键进行pickle处理。

在Python中,适当的方法是在Person类中实现u eq_uu和u ne_u_u函数,如下所示:

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会更有效。

在Python中,适当的方法是在Person类中实现uuu eq_uu和u ne_uu函数,如下所示:

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会更有效。

我相信您看到的问题来自搁置模块存储其值的方式。每个值都独立于工具架中的其他值进行pickle处理,这意味着如果将同一对象作为值插入多个键下,则不会在键之间保留标识。但是,如果单个值对同一对象有多个引用,则标识将保留在该单个值内

下面是一个例子:

>>> 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
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”键下,另一个在“另一个a”键下。它不起作用,因为单独的值被单独地pickle,因此它们之间的标识丢失

第二次打印测试是否保留了存储在“two_a_references”键下的对a的两个引用。由于该列表是一次性修改的,因此保留了该身份


因此,为了解决你的问题,你有几个选择。一种方法是避免对标识进行测试,并在各种对象类型中依赖_eq______)方法来确定两个对象在语义上是否相等,即使它们不是相同的对象。另一种方法是将所有数据捆绑到一个对象中,例如字典,然后使用pickle.dump保存该对象,并使用pickle.load恢复,而不是使用shelve,或者使用shelve文档链接的自适应对象,我相信您看到的问题来自于搁置模块存储其值的方式。每个值都独立于工具架中的其他值进行pickle处理,这意味着如果将同一对象作为值插入多个键下,则不会在键之间保留标识。但是,如果单个值对同一对象有多个引用,则标识将保留在该单个值内

下面是一个例子:

>>> 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
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”键下,另一个在“另一个a”键下。它不起作用,因为单独的值被单独地pickle,因此它们之间的标识丢失

第二次打印测试是否保留了存储在“two_a_references”键下的对a的两个引用。由于该列表是一次性修改的,因此保留了该身份

因此,为了解决你的问题,你有几个选择。一种方法是避免对标识进行测试,并在各种对象类型中依赖_eq______)方法来确定两个对象在语义上是否相等,即使它们不是相同的对象。另一种方法是将所有数据捆绑到一个对象中,例如,一个字典,然后使用pickle.dump保存,并使用pickle.load恢复,而不是使用shelve,或者您可以使用shelve或adapt,它是从shelve文档链接而来的,并且几乎可以做到这一点。

问题

要想用shelve保持身份,你需要用pickle保持身份

解决方案

此类保存其类站点上的所有对象,如果标识相同,则将其还原。您应该能够从中创建子类

>>> 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'
问题

要想用shelve保持身份,你需要用pickle保持身份

解决方案

此类保存其类站点上的所有对象,如果标识相同,则将其还原。您应该能够从中创建子类

>>> 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'

@user2357112只是一个示例类,不应该有什么区别。。。请参阅@user2357112,这只是一个示例类,不应该有什么区别。。。一目了然!事情
是的,我已经把我所有的数据捆绑在一个大字典上了,但是正如你提到的,身份并没有被保留,因为每个键都是分开的。我会尝试弄乱搁置选项,只使用pickle,但我怀疑这会有帮助,因为搁置是。只有使用和捆绑在一个大dict中的所有东西解决了问题。谢谢你的回答!确切地问题是,我已经将我的所有数据捆绑在一个大字典上,但正如您所提到的,身份并没有保留,因为每个密钥都是单独腌制的。我会尝试弄乱搁置选项,只使用pickle,但我怀疑这会有帮助,因为搁置是。只有使用和捆绑在一个大dict中的所有东西解决了问题。谢谢你的回答!