python 搁置:重新打开搁置后相同的对象变成不同的对象
python shelve: same objects become different objects after reopening shelve
我在使用搁置时看到了这种行为:
import shelve
my_shelve = shelve.open('/tmp/shelve', writeback=True)
my_shelve['a'] = {'foo': 'bar'}
my_shelve['b'] = my_shelve['a']
id(my_shelve['a']) # 140421814419392
id(my_shelve['b']) # 140421814419392
my_shelve['a']['foo'] = 'Hello'
my_shelve['a']['foo'] # 'Hello'
my_shelve['b']['foo'] # 'Hello'
my_shelve.close()
my_shelve = shelve.open('/tmp/shelve', writeback=True)
id(my_shelve['a']) # 140421774309128
id(my_shelve['b']) # 140421774307832 -> This is weird.
my_shelve['a']['foo'] # 'Hello'
my_shelve['b']['foo'] # 'Hello'
my_shelve['a']['foo'] = 'foo'
my_shelve['a']['foo'] # 'foo'
my_shelve['b']['foo'] # 'Hello'
my_shelve.close()
如您所见,重新打开货架时,以前是同一对象的两个对象现在是两个不同的对象。
- 有人知道这里发生了什么吗?
- 有人知道如何避免这种行为吗?
我正在使用 Python 3.7.0
Anybody knows what is happening here?
Python 变量是对对象的引用。当您键入
a = 123
在幕后,Python 正在创建一个新对象 int(123)
,然后 a
指向它。如果你再写
a = 456
然后 Python 正在创建一个不同的对象 int(456)
,并更新 a
以成为对新对象的引用。它不会像 C 语言中的变量赋值那样覆盖存储在名为 a
的框中的内容。由于 id()
returns 对象的内存地址(好吧,CPython 参考实现无论如何都会这样做),每次将 a
指向不同的对象时它都会有不同的值.
Anybody knows how to avoid this behavior?
你不能,因为这是 属性 赋值的工作方式。
shelve
将对象的 pickle 表示存储到 shelf 文件中。当您存储与 my_shelf['a']
和 my_shelf['b']
相同的对象时,shelve
为 'a'
键写入一个对象的 pickle,为 [=15] 写入另一个对象的 pickle =] 键。需要注意的一件重要事情是它会单独腌制所有值。
当您重新打开架子时,shelve
使用腌制的表示来重建对象。它使用 'a'
的 pickle 重建您存储的字典,并使用 'b'
的 pickle 再次重建您存储的字典.
pickle 不会相互影响,并且在未 pickle 时无法 return 彼此相同的对象。在磁盘上的表示中没有迹象表明 my_shelf['a']
和 my_shelf['b']
曾经是同一个对象;使用 my_shelf['a']
和 my_shelf['b']
的单独对象制作的架子看起来可能完全相同。
如果您想保留这些对象相同的事实,则不应将它们存储在一个架子的单独键中。考虑使用 'a'
和 'b'
键而不是使用 shelve
.
对单个字典进行 pickling 和 unpickling
有一种方法可以做到这一点,但它需要您自己制作 class,否则会变得更聪明。可以在pickle时注册原来的ids,设置unpickling函数,如果创建的对象被unpickle过,则查找它,如果没有,则创建它。
我有一个使用下面 __reduce__
的简单示例。但是您应该首先知道这并不是最好的主意。
使用 copyreg
库可能更容易,但您应该知道,您使用此库所做的任何事情都会影响您一直 pickle 的任何内容。 __reduce__
方法将更清晰、更安全,因为您明确告诉 pickle
您希望哪些 class 具有此行为,而不是将它们隐式应用于所有内容。
这个系统有更糟糕的警告。 id 将始终在 python 个实例之间更改,因此您需要在 __init__
(或 __new__
,无论您如何执行)期间存储原始 id,并确保保留现在已失效的值当它稍后被拉出货架时。由于垃圾收集,在 python 会话中甚至不能保证 id 的唯一性。我相信会出现其他不这样做的原因。 (我会尝试用我的 class 来解决这些问题,但我不做任何承诺。)
import uuid
class UniquelyPickledDictionary(dict):
_created_instances = {}
def __init__(self, *args, _uid=None, **kwargs):
super().__init__(*args, **kwargs)
self.uid = _uid
if _uid is None:
self.uid = uuid.uuid4()
UniquelyPickledDictionary._created_instances[self.uid] = self
def __reduce__(self):
return UniquelyPickledDictionary.create, (self.uid,), None, None, list(self.items())
@staticmethod
def create(uid):
if uid in UniquelyPickledDictionary._created_instances:
return UniquelyPickledDictionary._created_instances[uid]
return UniquelyPickledDictionary(_uid=uid)
uuid
库应该比长 运行 中的对象 ID 更独特。我忘了他们持有什么保证,but I believe this is not multiprocessing safe。
可以制作一个使用 copyreg 的等效版本来 pickle 任何 class,但需要对 unpickling 进行特殊处理以保证 repickling 指向同一对象。为了使其最通用,必须对 "already created" 字典进行检查以与所有实例进行比较。为了使其最可用,必须向实例添加一个新值,如果对象使用 __slots__
(或在其他一些情况下),这可能是不可能的。
我使用的是 3.6,但我认为它应该适用于任何仍受支持的 Python 版本。它在我的测试中保留了对象,递归(但 pickle 已经这样做了)和多次 unpicklings。
我在使用搁置时看到了这种行为:
import shelve
my_shelve = shelve.open('/tmp/shelve', writeback=True)
my_shelve['a'] = {'foo': 'bar'}
my_shelve['b'] = my_shelve['a']
id(my_shelve['a']) # 140421814419392
id(my_shelve['b']) # 140421814419392
my_shelve['a']['foo'] = 'Hello'
my_shelve['a']['foo'] # 'Hello'
my_shelve['b']['foo'] # 'Hello'
my_shelve.close()
my_shelve = shelve.open('/tmp/shelve', writeback=True)
id(my_shelve['a']) # 140421774309128
id(my_shelve['b']) # 140421774307832 -> This is weird.
my_shelve['a']['foo'] # 'Hello'
my_shelve['b']['foo'] # 'Hello'
my_shelve['a']['foo'] = 'foo'
my_shelve['a']['foo'] # 'foo'
my_shelve['b']['foo'] # 'Hello'
my_shelve.close()
如您所见,重新打开货架时,以前是同一对象的两个对象现在是两个不同的对象。
- 有人知道这里发生了什么吗?
- 有人知道如何避免这种行为吗?
我正在使用 Python 3.7.0
Anybody knows what is happening here?
Python 变量是对对象的引用。当您键入
a = 123
在幕后,Python 正在创建一个新对象 int(123)
,然后 a
指向它。如果你再写
a = 456
然后 Python 正在创建一个不同的对象 int(456)
,并更新 a
以成为对新对象的引用。它不会像 C 语言中的变量赋值那样覆盖存储在名为 a
的框中的内容。由于 id()
returns 对象的内存地址(好吧,CPython 参考实现无论如何都会这样做),每次将 a
指向不同的对象时它都会有不同的值.
Anybody knows how to avoid this behavior?
你不能,因为这是 属性 赋值的工作方式。
shelve
将对象的 pickle 表示存储到 shelf 文件中。当您存储与 my_shelf['a']
和 my_shelf['b']
相同的对象时,shelve
为 'a'
键写入一个对象的 pickle,为 [=15] 写入另一个对象的 pickle =] 键。需要注意的一件重要事情是它会单独腌制所有值。
当您重新打开架子时,shelve
使用腌制的表示来重建对象。它使用 'a'
的 pickle 重建您存储的字典,并使用 'b'
的 pickle 再次重建您存储的字典.
pickle 不会相互影响,并且在未 pickle 时无法 return 彼此相同的对象。在磁盘上的表示中没有迹象表明 my_shelf['a']
和 my_shelf['b']
曾经是同一个对象;使用 my_shelf['a']
和 my_shelf['b']
的单独对象制作的架子看起来可能完全相同。
如果您想保留这些对象相同的事实,则不应将它们存储在一个架子的单独键中。考虑使用 'a'
和 'b'
键而不是使用 shelve
.
有一种方法可以做到这一点,但它需要您自己制作 class,否则会变得更聪明。可以在pickle时注册原来的ids,设置unpickling函数,如果创建的对象被unpickle过,则查找它,如果没有,则创建它。
我有一个使用下面 __reduce__
的简单示例。但是您应该首先知道这并不是最好的主意。
使用 copyreg
库可能更容易,但您应该知道,您使用此库所做的任何事情都会影响您一直 pickle 的任何内容。 __reduce__
方法将更清晰、更安全,因为您明确告诉 pickle
您希望哪些 class 具有此行为,而不是将它们隐式应用于所有内容。
这个系统有更糟糕的警告。 id 将始终在 python 个实例之间更改,因此您需要在 __init__
(或 __new__
,无论您如何执行)期间存储原始 id,并确保保留现在已失效的值当它稍后被拉出货架时。由于垃圾收集,在 python 会话中甚至不能保证 id 的唯一性。我相信会出现其他不这样做的原因。 (我会尝试用我的 class 来解决这些问题,但我不做任何承诺。)
import uuid
class UniquelyPickledDictionary(dict):
_created_instances = {}
def __init__(self, *args, _uid=None, **kwargs):
super().__init__(*args, **kwargs)
self.uid = _uid
if _uid is None:
self.uid = uuid.uuid4()
UniquelyPickledDictionary._created_instances[self.uid] = self
def __reduce__(self):
return UniquelyPickledDictionary.create, (self.uid,), None, None, list(self.items())
@staticmethod
def create(uid):
if uid in UniquelyPickledDictionary._created_instances:
return UniquelyPickledDictionary._created_instances[uid]
return UniquelyPickledDictionary(_uid=uid)
uuid
库应该比长 运行 中的对象 ID 更独特。我忘了他们持有什么保证,but I believe this is not multiprocessing safe。
可以制作一个使用 copyreg 的等效版本来 pickle 任何 class,但需要对 unpickling 进行特殊处理以保证 repickling 指向同一对象。为了使其最通用,必须对 "already created" 字典进行检查以与所有实例进行比较。为了使其最可用,必须向实例添加一个新值,如果对象使用 __slots__
(或在其他一些情况下),这可能是不可能的。
我使用的是 3.6,但我认为它应该适用于任何仍受支持的 Python 版本。它在我的测试中保留了对象,递归(但 pickle 已经这样做了)和多次 unpicklings。