Страницы

Поиск по вопросам

суббота, 11 января 2020 г.

Передача статического метода класса в multiprocessing.Process на Питоне 3.4

#python #python_3x #multiprocessing #python_36


Пытаюсь запустить статический метод класса через multiprocessing.Process. 

import multiprocessing as mp

class Test:
    @staticmethod
    def method():
        pass

if __name__=="__main__":       
    proc = mp.Process(target=Test.method)
    proc.start()


Но сталкиваюсь с тем, что оно работает в питоне 3.6, но не работает в 3.4 (а для
проекта требуется поддержка питона с 3.4). Полистав документацию, нашел, что можно
передавать только методы, определенные в top level'е модуля. Но тогда почему это работает
в питоне 3.6? Посмотрев чейнджлоги, я не нашел изменения в этой механике. Соответственно
встали вопросы:


Можно ли как то завернуть произвольную функцию или метод так, чтобы ее можно было
передать в Process.


Для отдельно взятого метода можно сделать функцию-обертку на уровне модуля:

import multiprocessing as mp

class Test:
    @staticmethod
    def method():
        pass

def method_wrapper(): # <======= This
    return Test.method()

if __name__=="__main__":       
    proc = mp.Process(target=method_wrapper)
    proc.start()


но это не подойдет, когда таких функций много. Можно ли сделать какую-то функцию,
которая будет принимать нужный мне метод и возвращать что-то, что можно передать в Process


Соответствует ли поведение питона 3.6 тому, что в документации (или это баг/фича)?
То есть могу ли я рассчитывать, что в будущем это поведение не изменится?


P.S. 

Зачем мне это нужно? Глобально стоит задача ограничить время выполнения функций.
То есть есть скрипт, который выполняет разные действия, и некоторые из них могут быть
очень долгие, и нужно их прибить, если они выполняются дольше определенного времени,
и сделать что-то с этим (или подождать и попробовать еще раз, или начать откат).  То
есть что я пытаюсь сделать: запустить функцию/метод в отдельном процессе, подождать
его в главном некоторое время и убить, если надо.

Потоки не подходят, я не могу их убить. Мне нужно гарантировать, что если время истекло,
действие больше не выполняется, прежде чем работать дальше.

В общем если есть идеи, как это сделать по другому, буду рад идеям.
    


Ответы

Ответ 1



Если я правильно понимаю, у Вас trace при ошибке получается примерно таким (проверял на Python 3.4.4, Windows 7 64-bit): Traceback (most recent call last): File "multiproc.py", line 17, in proc.start() File "c:\Python34\lib\multiprocessing\process.py", line 105, in start self._popen = self._Popen(self) File "c:\Python34\lib\multiprocessing\context.py", line 212, in _Popen return _default_context.get_context().Process._Popen(process_obj) File "c:\Python34\lib\multiprocessing\context.py", line 313, in _Popen return Popen(process_obj) File "c:\Python34\lib\multiprocessing\popen_spawn_win32.py", line 66, in __init__ reduction.dump(process_obj, to_child) File "c:\Python34\lib\multiprocessing\reduction.py", line 59, in dump ForkingPickler(file, protocol).dump(obj) _pickle.PicklingError: Can't pickle : attribute lookup method on __main__ failed ... File "", line 1, in File "c:\Python34\lib\multiprocessing\spawn.py", line 100, in spawn_main new_handle = steal_handle(parent_pid, pipe_handle) File "c:\Python34\lib\multiprocessing\reduction.py", line 81, in steal_handle _winapi.PROCESS_DUP_HANDLE, False, source_pid) OSError: [WinError 87] Параметр задан неверно Обращает на себя внимание строка Can't pickle . И действительно, до версии Python 3.5 pickle не поддерживал упаковку bound-методов. Начиная с версии 3.5 поддерживает. Итого Если есть необходимость использовать именно версию 3.4, то можно воспользоваться одним из советов: Can't pickle when using multiprocessing Pool.map() или Pickling a staticmethod in Python (правда, нужно будет дополнить код обёртками). Начиная с версии 3.5 всё работает и будет работать. Варианты доработок для Python 3.4 Wrapper По совету @jfs в комментарии выше: def wrapper(klass, method): getattr(klass, method)() if __name__ == "__main__": proc = mp.Process(target=wrapper, args=(Test, "method")) proc.start() Использовать метакласс По ответу с основного Stackoverflow: http://stackoverflow.com/a/1914798/711380 class _PickleableStaticMethod(object): def __init__(self, fn, cls=None): self.cls = cls self.fn = fn def __call__(self, *args, **kwargs): return self.fn(*args, **kwargs) def __get__(self, obj, cls): return _PickleableStaticMethod(self.fn, cls) def __getstate__(self): return (self.cls, self.fn.__name__) def __setstate__(self, state): self.cls, name = state self.fn = getattr(self.cls, name).fn class pickleable_staticmethods(type): def __new__(cls, name, bases, dct): new_cls = type.__new__(cls, name, bases, dct) dct = new_cls.__dict__ for name in dct.keys(): value = new_cls.__dict__[name] if isinstance(value, staticmethod): setattr( new_cls, name, _PickleableStaticMethod(value.__get__(None, new_cls), new_cls)) return new_cls class Test(metaclass=pickleable_staticmethods): @staticmethod def method(): while True: print(time.ctime()) time.sleep(2) if __name__ == "__main__": proc = mp.Process(target=Test.method) proc.start()

Комментариев нет:

Отправить комментарий