Python decorator with multiprocessing fails

前端 未结 3 456
醉话见心
醉话见心 2020-12-17 10:38

I would like to use a decorator on a function that I will subsequently pass to a multiprocessing pool. However, the code fails with \"PicklingError: Can\'t pickle : attribut

3条回答
  •  执笔经年
    2020-12-17 11:03

    If you want the decorators too bad (like me), you can also use the exec() command on the function string, to circumvent the mentioned pickling.

    I wanted to be able to pass all the arguments to an original function and then use them successively. The following is my code for it.

    At first, I made a make_functext() function to convert the target function object to a string. For that, I used the getsource() function from the inspect module (see doctumentation here and note that it can't retrieve source code from compiled code etc.). Here it is:

    from inspect import getsource
    
    def make_functext(func):
        ft = '\n'.join(getsource(func).split('\n')[1:]) # Removing the decorator, of course
        ft = ft.replace(func.__name__, 'func')          # Making function callable with 'func'
        ft = ft.replace('#§ ', '').replace('#§', '')    # For using commented code starting with '#§'
        ft = ft.strip()                                 # In case the function code was indented
        return ft
    

    It is used in the following _worker() function that will be the target of the processes:

    def _worker(functext, args):
        scope = {}               # This is needed to keep executed definitions
        exec(functext, scope)
        scope['func'](args)      # Using func from scope
    

    And finally, here's my decorator:

    from multiprocessing import Process 
    
    def parallel(num_processes, **kwargs):
        def parallel_decorator(func, num_processes=num_processes):
            functext = make_functext(func)
            print('This is the parallelized function:\n', functext)
            def function_wrapper(funcargs, num_processes=num_processes):
                workers = []
                print('Launching processes...')
                for k in range(num_processes):
                    p = Process(target=_worker, args=(functext, funcargs[k])) # use args here
                    p.start()
                    workers.append(p)
            return function_wrapper
        return parallel_decorator
    

    The code can finally be used by defining a function like this:

    @parallel(4)
    def hello(args):
        #§ from time import sleep     # use '#§' to avoid unnecessary (re)imports in main program
        name, seconds = tuple(args)   # unpack args-list here
        sleep(seconds)
        print('Hi', name)
    

    ... which can now be called like this:

    hello([['Marty', 0.5],
           ['Catherine', 0.9],
           ['Tyler', 0.7],
           ['Pavel', 0.3]])
    

    ... which outputs:

    This is the parallelized function:
     def func(args):
            from time import sleep
            name, seconds = tuple(args)
            sleep(seconds)
            print('Hi', name)
    Launching processes...
    Hi Pavel
    Hi Marty
    Hi Tyler
    Hi Catherine
    

    Thanks for reading, this is my very first post. If you find any mistakes or bad practices, feel free to leave a comment. I know that these string conversions are quite dirty, though...

提交回复
热议问题