实际上我不确定标题是否恰当地描述了问题。让我展示代码。
import os
from multiprocessing import JoinableQueue
# A dict-like class, but is able to be accessed by attributes.
# example: d = AttrDict({'a': 1, 'b': 2})
# d.a is equivalent to d['a']
class AttrDict(dict):
def __init__(self, *args, **kwargs):
super(AttrDict, self).__init__(*args, **kwargs)
self.__dict__ = self
queue = JoinableQueue()
pid = os.fork()
if pid == 0:
d = AttrDict({'a': 1, 'b': 2})
queue.put(d)
queue.join()
os._exit(0)
else:
d = queue.get()
queue.task_done()
#d = AttrDict(d.items()) #(1)
d.a = 3 #(2)
#d['a'] = 3 #(3)
print d
上面的代码打印{'a': 1, 'b': 2}
,这意味着(2)没有任何效果。
如果我将(2)更改为(3)或启用(1),则输出为{'a': 3, 'b': 2}
,这是预期的。
当d
通过队列传递时,似乎发生了一些事情。
使用Python 2.7进行测试。
解决方案:
正如@kindall和@Blckknght所指出的那样,原因是d
被选为dict,当它被queue.get()
取消时,self.__dict__ = self
魔法未设置。 print d.__dict__
和print d
可能会出现差异。
为了设置魔法,我将方法__setstate__
添加到AttrDict
:
class AttrDict(dict):
def __init__(self, *args, **kwargs):
super(AttrDict, self).__init__(*args, **kwargs)
self.__dict__ = self
def __setstate__(self, state):
self.__dict__ = state
现在代码按预期工作。
答案 0 :(得分:1)
我的猜测是,因为它是dict
的子类,所以AttrDict
被序列化为dict
。特别是指向__dict__
的{{1}}可能不会被保留。您可以使用某些魔术方法自定义序列化;见this article。
答案 1 :(得分:1)
这不是一个多处理问题,因为mutlprocessing.Queue
使用pickle
来序列化和反序列化您通过它发送的对象。问题在于pickle
未能正确保留"魔法"设置self.__dict__ = self
时获得的行为。
如果您检查子进程中的对象,您会发现其__dict__
只是一个普通字典,其内容与对象本身相同。在对象上设置新属性时,其__dict__
会更新,但继承的字典self
不会更新。这就是我的意思:
>>> d = AttrDict({"a":1, "b":2})
>>> d2 = pickle.loads(pickle.dumps(d, -1))
>>> d2
{'a': 1, 'b': 2}
>>> d2.b = 3
>>> d2
{'a': 1, 'b': 2}
>>> d2.__dict__
{'a': 1, 'b': 3}
虽然您可以深入了解pickle
如何工作的详细信息并让您的序列化再次发挥作用,但我认为更简单的方法是通过让您的类覆盖{{1}来依赖不那么神奇的行为},__getattr__
和__setattr__
方法:
__delattr__
此类的实例将像您自己的实例一样工作,但它们可以成功地进行pickle和unpickled:
class AttrDict(dict):
__slots__ = () # we don't need a __dict__
def __getattr__(self, name): # wrapper around dict.__setitem__, with an exception fix
try:
return self[name]
except KeyError:
raise AttributeError(name) from None # raise the right type of exception
def __delattr__(self, name): # wrapper around dict.__delitem__
try:
del self[name]
except KeyError:
raise AttributeError(name) from None # change exception type here too
__setattr__ = dict.__setitem__ # no special exception rewriting needed here