泡菜转储的多处理队列问题

Posted

技术标签:

【中文标题】泡菜转储的多处理队列问题【英文标题】:multiprocessing queue issue with pickle dumps 【发布时间】:2019-08-10 11:37:27 【问题描述】:

我已经阅读并再次阅读了有关多处理模块和队列管理的 Python 文档,但我找不到与此问题相关的任何内容,这让我发疯并阻止了我的项目:

我编写了一个“JsonLike”类,它允许我创建一个对象,例如:

a = JsonLike()
a.john.doe.is.here = True

...不考虑中间初始化(非常有用)

以下代码只是创建了这样一个对象,将其设置并插入到数组中并尝试将其发送到进程(这是我需要的,但是发送对象本身会导致相同的错误强>)

考虑到这段代码:

from multiprocessing import Process, Queue, Event

class JsonLike(dict):
    """
    This class allows json-crossing-through creation and setting such as :
    a = JsonLike()
    a.john.doe.is.here = True
    it automatically creates all the hierarchy
    """

    def __init__(self, *args, **kwargs):
        # super(JsonLike, self).__init__(*args, **kwargs)
        dict.__init__(self, *args, **kwargs)
        for arg in args:
            if isinstance(arg, dict):
                for k, v in arg.items():
                    self[k] = v
        if kwargs:
            for k, v in kwargs.items():
                self[k] = v

    def __getattr__(self, attr):
        if self.get(attr) != None:
            return attr
        else:
            newj = JsonLike()
            self.__setattr__(attr, newj)
            return newj

    def __setattr__(self, key, value):
        self.__setitem__(key, value)

    def __setitem__(self, key, value):
        dict.__setitem__(self, key, value)
        self.__dict__.update(key: value)

    def __delattr__(self, item):
        self.__delitem__(item)

    def __delitem__(self, key):
        dict.__delitem__(self, key)
        del self.__dict__[key]


def readq(q, e):
    while True:
        obj = q.get()
        print('got')
        if e.is_set():
            break


if __name__ == '__main__':
    q = Queue()
    e = Event()

    obj = JsonLike()
    obj.toto = 1

    arr=[obj]

    proc = Process(target=readq, args=(q,e))
    proc.start()
    print(f"Before sending value :arr")
    q.put(arr)
    print('sending done')
    e.set()
    proc.join()
    proc.close()

我得到以下输出(在q.put):

Before sending value :['toto': 1]
Traceback (most recent call last):
sending done
  File "/usr/lib/python3.7/multiprocessing/queues.py", line 236, in _feed
    obj = _ForkingPickler.dumps(obj)
  File "/usr/lib/python3.7/multiprocessing/reduction.py", line 51, in dumps
    cls(buf, protocol).dump(obj)
TypeError: 'JsonLike' object is not callable

有什么建议吗?

【问题讨论】:

呃,不是很相关,而且更像是我的一个小便,但这不是“Json-like”,它是 dict like。 JSON 是一种基于文本的序列化格式。当你反序列化它时,它不再是 JSON。现在是一些物化的数据结构 好吧,让我们说类似javascript的json getter/setter,但正如你所说,与暴露的问题无关。 【参考方案1】:

问题是你在搞乱__getattr__。如果你在这个方法中添加一个 print 语句,你会看到运行下面的代码也会导致崩溃:

obj = JsonLike()
obj.toto.test = 1

q = Queue()
q.put(obj)
q.get()

最后一条语句将导致(重复)调用obj.__getattr__,搜索名为__getstate__ 的属性(稍后它将尝试找到它的朋友__setstate__)。以下是pickle 文档中关于这种 dunder 方法的内容:

如果__getstate__() 方法不存在,实例的__dict__ 将照常腌制。

在您的情况下,问题在于此方法不存在,但您的代码使其看起来像它一样(通过动态创建具有正确名称的属性)。因此,不会触发默认行为,而是调用名为 __getstate__ 的空属性。问题是 __getstate__ 不是可调用的,因为它是一个空的 JsonLike 对象。这就是为什么您可能会在此处看到“JsonLike is not callable”之类的错误弹出窗口。

一个快速解决方法是避免接触看起来像__xx__ 甚至_xx 的属性。就此而言,您可以添加/修改这些行:

import re

dunder_pattern = re.compile("__.*__")
protected_pattern = re.compile("_.*")

class JsonLike(dict):

    def __getattr__(self, attr):
        if dunder_pattern.match(attr) or protected_pattern.match(attr):
            return super().__getattr__(attr)
        if self.get(attr) != None:
            return attr
        else:
            newj = JsonLike()
            self.__setattr__(attr, newj)
            return newj

这将允许之前的代码工作(您的代码也是如此)。但另一方面,你将不能再写像obj.__toto__ = 1 这样的东西,但这可能是件好事。

我觉得您可能会在其他情况下遇到类似的错误,遗憾的是,在某些情况下您会发现库不会使用这种可预测的属性名称。这就是为什么我不建议使用这种机制 IRL 的原因之一(尽管我真的很喜欢这个想法,我很想看看这能走多远)。

【讨论】:

非常感谢,你帮了我很多!我忘了仔细检查有关它的 Pickle 文档。现在很清楚了。 如果您不介意,我会尝试找到一个主题标题/标签,以便遇到类似问题的人会有一些提示(即使有人碰巧遇到这种情况的可能性很小)类似的问题)。 @eglacet,我遇到了 JSON 序列化对象的类似问题,但此解决方案不适用于我的问题。你能帮我解决我的问题吗? link。感谢您在这方面的帮助。

以上是关于泡菜转储的多处理队列问题的主要内容,如果未能解决你的问题,请参考以下文章

将列表转储到pickle文件中并稍后检索[关闭]

加载或倾倒泡菜时如何阻止动画 QCursor 冻结?

加载泡菜 NotFittedError:CountVectorizer - 未安装词汇

分析线程转储 - sun.misc.Unsafe.park 上的许多阻塞线程 [重复]

多线程应用程序中的日志队列将信息转储到数据库(服务器端应用程序)?

信号处理程序后信号无法生成核心转储