通过多处理传递类似dict的对象.Queue使其无法被属性修改

时间:2014-03-18 01:16:57

标签: python multiprocessing

实际上我不确定标题是否恰当地描述了问题。让我展示代码。

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

现在代码按预期工作。

2 个答案:

答案 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