проблема многопроцессорной очереди с дампами - PullRequest
1 голос
/ 19 марта 2019

Я прочитал и снова прочитал документацию 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

Есть предложения?

1 Ответ

2 голосов
/ 20 марта 2019

Проблема в том, что вы связываетесь с __getattr__. Если вы добавите оператор print внутри этого метода, вы увидите, что выполнение следующего кода также приводит к сбою:

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

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

Это последнее утверждение приведет к вызову (неоднократно) obj.__getattr__ в поиске атрибута с именем __getstate__ (позже он попытается найти своего друга __setstate__) , Вот что документация pickle говорит об этом более сложном методе:

Если метод __getstate__() отсутствует, экземпляр __dict__ экземпляра протравливается как обычно.

В вашем случае проблема в том, что этот метод не существует, но ваш код заставляет его выглядеть так, как он есть (создавая атрибут с правильным именем на лету). Поэтому поведение по умолчанию не запускается, вместо этого вызывается пустой атрибут с именем __getstate__. Проблема в том, что __getstate__ не вызывается, поскольку это пустой JsonLike объект. Вот почему вы можете увидеть здесь такие ошибки, как «JsonLike 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 (хотя мне очень нравилась эта идея, и я хотел бы посмотреть, как далеко это может зайти).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...