报错/wins系统下,Linux系统无报错

  1. def decorator_timer(func):
  2. def wrapper(*args,**kwargs):
  3. t1 = timeit.default_timer()
  4. func(*args, **kwargs)
  5. t2 = timeit.default_timer()
  6. print(f'{func.__name__}() has been running for "{round(t2-t1,3)}s"...')
  7. return func
  8. return wrapper

在win系统中使用装饰器装饰子进程的函数时,会有下面报错

Traceback (most recent call last): File “G:/code_workspace/py3_2020/8_MultiThreading/2_multiprocess.py”, line 34, in

  1. work_p.start()

File “G:\Anaconda\lib\multiprocessing\process.py”, line 112, in start

self._popen = self._Popen(self)

File “G:\Anaconda\lib\multiprocessing\context.py”, line 223, in _Popen

return _default_context.get_context().Process._Popen(process_obj)

File “G:\Anaconda\lib\multiprocessing\context.py”, line 322, in _Popen

return Popen(process_obj)

File “G:\Anaconda\lib\multiprocessing\popenspawnwin32.py”, line 65, in __init

reduction.dump(process_obj, to_child)

File “G:\Anaconda\lib\multiprocessing\reduction.py”, line 60, in dump

ForkingPickler(file, protocol).dump(obj)

AttributeError: Can’t pickle local object ‘decorator_timer..wrapper’

被装饰器装饰的函数无法多进程原因

python多进程的原理是通过pickle多进程函数名,然后新建一个子进程并在子进程中导入模块后unpickle,通过访问模块的该函数来实现函数在子进程中的运行的。
就是说多进程获取返回值是需要序列化的,但是装饰器是无法序列化的,所以报错。查阅资料找到解决办法为:functools.wraps(func),它可以解除这种束缚,从而变的可序列化。

关于多进程编程中的pickle的一个重点是,对于多进程函数的pickle,只会pickle其函数名,也即f.name属性,然后通过模块的点号访问,因此该函数必须是可以通过模块点号访问到的,这也就是为什么要求被pickle必须被定义在模块的顶层。

问题在于,当一个函数被装饰器修饰过后,根据装饰器的语法规则,实际上是对被装饰器函数复制了一个新的wrapper函数的引用,这时被装饰函数的名称属性就发生了改变,变成了wrapper的名称,更具体的关于装饰器语法的说明可看笔者这篇文章这篇文章。并且由于wrapper函数是定义在装饰器函数中的,即非模块顶层函数,无法通过点号访问到,因此这时如果直接将被装饰函数用以多进程运行,会报错类似这样的错误”AttributeError: Can’t pickle local object ‘decorator..wrapper”。一个典型的错误实例代码如下所示。

导入functools模块中的wraps()方法解决问题

import timeit
import time
import multiprocessing
import functools
import os

def decorator_timer(func):

    @functools.wraps(func)
    def wrapper(*args,**kwargs):
        t1 = timeit.default_timer()
        func(*args, **kwargs)
        t2 = timeit.default_timer()
        print(f'{func.__name__}() has been running for "{round(t2-t1,3)}s"...')
        return func

    return wrapper

@decorator_timer
def work():
    for i in range(5):
        print(f'working...')
        time.sleep(0.2)
    print('work_p finished!')


if __name__ == '__main__':
    work_p = multiprocessing.Process(target=work,daemon=True)
    # work_p.daemon = True
    work_p.start()

    # 主进程等待1s
    time.sleep(0.5)
    print('main process finished!')
working...
working...
working...
main process finished!

Process finished with exit code 0