Многопроцессный Python с модулями importlib - PullRequest
2 голосов
/ 22 марта 2019

Доброго вам дня,
Сегодня я перемещал код с threading на multiprocess. Казалось, все в порядке, пока я не получил следующую ошибку:

Error

Traceback (most recent call last):
  File "run.py", line 93, in <module>
    main()
  File "run.py", line 82, in main
    emenu.executemenu(components, _path)
  File "/home/s1810979/paellego/lib/execute/execute_menu.py", line 29, in executemenu
    e.executeall(installed, _path)
  File "/home/s1810979/paellego/lib/execute/execute.py", line 153, in executeall
    pool.starmap(phase2, args)
  File "/usr/lib64/python3.4/multiprocessing/pool.py", line 268, in starmap
    return self._map_async(func, iterable, starmapstar, chunksize).get()
  File "/usr/lib64/python3.4/multiprocessing/pool.py", line 608, in get
    raise self._value
  File "/usr/lib64/python3.4/multiprocessing/pool.py", line 385, in _handle_tasks
    put(task)
  File "/usr/lib64/python3.4/multiprocessing/connection.py", line 206, in send
    self._send_bytes(ForkingPickler.dumps(obj))
  File "/usr/lib64/python3.4/multiprocessing/reduction.py", line 50, in dumps
    cls(buf, protocol).dump(obj)
_pickle.PicklingError: Can't pickle <class 'module'>: attribute lookup module on builtins failed

Код

execute.py

def executeall(components, _path):
    args = []
    manager = multiprocessing.Manager()
    q = manager.Queue()

    resultloc = '/some/result.log'
    for component in components:
        for apkpath, resultpath in zip(execonfig.apkpaths, execonfig.resultpaths):
            args.append((component,apkpath,resultpath,q,)) #Args for subprocesses
    cores = askcores()
    with multiprocessing.Pool(processes=cores) as pool:
        watcher = pool.apply_async(lgr.log, (resultloc+'/results.txt', q,))

        pool.starmap(phase2, args)

component.py

class Component(object):
    def __init__(self, installmodule, runmodule, installerloc, installationloc, dependencyloc):
        self.installmodule = installmodule
        self.runmodule = runmodule
        self.installerloc = installerloc
        self.installationloc = installationloc
        self.dependencyloc = dependencyloc
        self.config = icnf.Installconfiguration(installerloc+'/conf.conf')
    #lots of functions...

installconfig.py

class State(Enum):
    BEGIN=0 #Look for units
    UNIT=1  #Look for unit keypairs
    KEYPAIR=3

class Phase(Enum):
    NONE=0
    DEPS=1
    PKGS=2

class Installconfiguration(object):
    def __init__(self, config):
        dictionary = self.reader(config) #Fill a dictionary
        #dictionary (key:Phase, value: (dictionary key: str, job))
        self.deps = dictionary[Phase.DEPS]
        self.pkgs = dictionary[Phase.PKGS]

job.py

class Job(object):
    def __init__(self, directory=None, url=None):
        self.directory = directory if directory else ''
        self.url = url if url else ''

Как видите, я передаю компонент в качестве аргумента function phase2(component, str, str, multiprocess.manager.Queue()).

Второй и третий аргумент конструктора component - это модули, импортированные с importlib.


Что я пробовал

Я новичок в python, но не в программировании. Вот что я попробовал:

  • Поскольку в самой ошибке не указывалось, в чем именно проблема, я попытался удалить аргументы, чтобы выяснить, какие из них не могут быть засолены: Удалите component, и все работает нормально, так что, похоже, это является причиной проблемы , Однако мне нужно, чтобы этот объект был передан моим процессам.
  • Я искал в Интернете несколько часов, но не нашел ничего, кроме базовых руководств по многопроцессорной обработке и объяснений о том, как работает рассол. Я нашел это , говорящее, что это должно работать, но не на окнах или чем-то. Тем не менее, он не работает на Unix (который я использую)

Мои идеи

Насколько я понял, ничто не говорит о том, что я не могу отправить класс, содержащий два модуля importlib. Я не знаю, в чем именно проблема с классом component, но модуль importlib как члены - единственные нерегулярные вещи. Вот почему я считаю, что проблема возникает здесь.


Вопрос

Знаете ли вы, почему класс, содержащий модули, не подходит для «засолки»? Как можно лучше понять, почему и где происходят ошибки Can't pickle <class 'module'> 1054 *

больше код

Полный исходный код для этого можно найти на https://github.com/Sebastiaan-Alvarez-Rodriguez/paellego

Вопросы ко мне

Пожалуйста, оставляйте комментарии с просьбой дать разъяснения / дополнительные фрагменты кода / ??? если вы хотите, чтобы я отредактировал этот вопрос

Последний запрос

Я бы хотел, чтобы решения использовали только стандартную библиотеку python, желательно python 3.3. Кроме того, требование моего кода заключается в том, что он работает в системах Unix.

Заранее спасибо


Редактировать

В соответствии с запросом приведен минимальный пример, который значительно упрощает проблему:
main.py (вы можете выполнить как python main.py foo)

#!/usr/bin/env python
import sys
import importlib
import multiprocessing
class clazz(object):
    def __init__(self, moduly):
        self.moduly = moduly

    def foopass(self, stringy):
        self.moduly.foo(stringy)

    def barpass(self, stringy, numbery):
        self.moduly.bar(stringy)
        print('Second argument: '+str(numbery))

def worker(clazzy, numbery):
    clazzy.barpass('wow', numbery)

def main():
    clazzy = clazz(importlib.import_module(sys.argv[1]))
    clazzy.foopass('init')

    args = [(clazzy, 2,)]
    with multiprocessing.Pool(processes=2) as pool:
        pool.starmap(worker, args)

if __name__ == "__main__":
    main()

foo.py (должен находиться в том же каталоге, что и для предложенного выше вызова):

#!/usr/bin/env python
globaly = 0

def foo(stringy):
    print('foo '+stringy)
    global globaly
    globaly = 5

def bar(stringy):
    print('bar '+stringy)
    print(str(globaly))

Это дает ошибку при запуске: TypeError: can't pickle module objects Теперь мы знаем, что травление объектов модуля (к сожалению) невозможно.

1 Ответ

1 голос
/ 23 марта 2019

Чтобы избавиться от ошибки, пусть clazz не принимает модуль в качестве атрибута, каким бы удобным он ни был, но пусть он принимает "modpath", что является обязательной строкой для importlib для импорта указанного модуля.пользователем.
Это выглядит так (foo.py остается точно таким же, как указано выше):

#!/usr/bin/env python
import sys
import importlib
import multiprocessing
class clazz(object):
    def __init__(self, modpathy):
        self.modpathy = modpathy

    def foopass(self, stringy):
        moduly = importlib.import_module(self.modpathy)
        moduly.foo(stringy)

    def barpass(self, stringy, numbery):
        moduly = importlib.import_module(self.modpathy)
        moduly.bar(stringy)
        print('Second argument: '+str(numbery))

def worker(clazzy, number):
    clazzy.barpass('wow', number)

def main():
    clazzy = clazz(sys.argv[1])
    clazzy.foopass('init')

    args = [(clazzy, 2,)]
    with multiprocessing.Pool(processes=2) as pool:
        pool.starmap(worker, args)

if __name__ == "__main__":
    main()

Если вам требуется, чтобы ваши глобальные переменные, такие как globaly, гарантированно поддерживали состояние,затем вам нужно передать изменяемый объект (например, список, словарь) для хранения этих данных, спасибо @DavisHerring:

Атрибуты модуля называются «глобальными переменными» в Python, но они больше не являются постоянными илидоступнее, чем любые другие данные.Почему бы просто не использовать словари?

Пример кода будет выглядеть следующим образом:

#!/usr/bin/env python
import sys
import importlib
import multiprocessing
class clazz(object):
    def __init__(self, modpathy):
        self.modpathy = modpathy
        self.dictionary = {}

    def foopass(self, stringy):
        moduly = importlib.import_module(self.modpathy)
        moduly.foo(stringy, self.dictionary)

    def barpass(self, stringy, numbery):
        moduly = importlib.import_module(self.modpathy)
        moduly.bar(stringy, self.dictionary)
        print('Second argument: '+str(numbery))

def worker(clazzy, number):
    clazzy.barpass('wow', number)

def main():
    clazzy = clazz(sys.argv[1])
    clazzy.foopass('init')

    args = [(clazzy, 2,)]
    with multiprocessing.Pool(processes=2) as pool:
        pool.starmap(worker, args)

if __name__ == "__main__":
    main()

foo.py (не более глобальных):

#!/usr/bin/env python

def foo(stringy, dictionary):
    print('foo '+stringy)
    globaly = 5
    dictionary['globaly'] = globaly

def bar(stringy, dictionary):
    print('bar '+stringy)
    globaly = dictionary['globaly']
    print(str(globaly))

Таким образом, вы можете обойти эту проблему, не раздражая can't pickle ... ошибок и сохраняя состояния

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