运行多处理Python代码时cProfile导致酸洗错误

时间:2018-12-21 20:55:06

标签: python pickle python-multiprocessing cprofile

我有一个Python脚本,当我正常运行它时,它可以很好地运行:

$ python script.py <options>

我正在尝试使用cProfile模块来分析代码:

$ python -m cProfile -o script.prof script.py <options>

当我启动上述命令时,我收到一个关于无法使函数腌制的错误:

Traceback (most recent call last):
  File "scripts/process_grid.py", line 1500, in <module>
    _compute_write_index(kwrgs)
  File "scripts/process_grid.py", line 626, in _compute_write_index
    args,
  File "scripts/process_grid.py", line 1034, in _parallel_process
    pool.map(_apply_along_axis_palmers, chunk_params)
  File "/home/james/miniconda3/envs/climate/lib/python3.6/multiprocessing/pool.py", line 266, in map
    return self._map_async(func, iterable, mapstar, chunksize).get()
  File "/home/james/miniconda3/envs/climate/lib/python3.6/multiprocessing/pool.py", line 644, in get
    raise self._value
  File "/home/james/miniconda3/envs/climate/lib/python3.6/multiprocessing/pool.py", line 424, in _handle_tasks
    put(task)
  File "/home/james/miniconda3/envs/climate/lib/python3.6/multiprocessing/connection.py", line 206, in send
    self._send_bytes(_ForkingPickler.dumps(obj))
  File "/home/james/miniconda3/envs/climate/lib/python3.6/multiprocessing/reduction.py", line 51, in dumps
    cls(buf, protocol).dump(obj)
_pickle.PicklingError: Can't pickle <function _apply_along_axis_palmers at 0x7fe05a540b70>: attribute lookup _apply_along_axis_palmers on __main__ failed

代码使用多重处理,我认为这是进行酸洗的地方。

正在播放的代码为here on GitHub

基本上,我在进程池中映射了一个函数和一个对应的参数字典:

pool.map(_apply_along_axis_palmers, chunk_params)

据我所知,函数_apply_along_axis_palmers是“可拾取的”,因为它是在模块的顶层定义的。再次在cProfile上下文之外运行时不会发生此错误,是否可能为酸洗添加了其他约束?

任何人都可以评论为什么会发生这种情况,和/或如何纠正该问题吗?

预先感谢您的任何建议。

1 个答案:

答案 0 :(得分:3)

您遇到的问题是,通过使用-mcProfile,模块__main__cProfile(代码的实际入口点),而不是脚本。 cProfile 尝试来解决此问题,方法是确保脚本在运行时将__name__视为"__main__",因此它知道它是作为脚本运行的,而不是导入的作为一个模块,但是sys.modules['__main__']仍然是cProfile模块。

问题是,pickle仅通过腌制其合格名称来处理腌制功能(加上一些样板代码可以说是一开始的功能)。为了确保在往返过程中仍然生存,它始终会仔细检查可在sys.modules中查找合格名称。因此,当您执行pickle.dumps(_apply_along_axis_palmers)(显式或隐式地通过将其作为映射器函数传递)时,在主脚本中定义了_apply_along_axis_palmers时,它将再次检查sys.modules['__main__']._apply_along_axis_palmers是否存在。但这不存在,因为cProfile._apply_along_axis_palmers不存在。

我不知道有什么 good 解决方案。我能想到的最好的办法是手动修复sys.modules,以正确公开您的模块及其内容。我尚未对此进行完整的测试,因此可能会有一些古怪之处,但我发现的解决方案是更改名为mymodule.py的模块,其形式为:

# imports...
# function/class/global defs...

if __name__ == '__main__':
    main()  # Or series of statements

收件人:

# imports...
import sys
# function/class/global defs...

if __name__ == '__main__':
    import cProfile
    # if check avoids hackery when not profiling
    # Optional; hackery *seems* to work fine even when not profiling, it's just wasteful
    if sys.modules['__main__'].__file__ == cProfile.__file__:
        import mymodule  # Imports you again (does *not* use cache or execute as __main__)
        globals().update(vars(mymodule))  # Replaces current contents with newly imported stuff
        sys.modules['__main__'] = mymodule  # Ensures pickle lookups on __main__ find matching version
    main()  # Or series of statements

从此开始,sys.modules['__main__']指的是您自己的模块,而不是cProfile,因此一切似乎正常。尽管如此,cProfile似乎仍然可以工作,酸洗可以按预期找到您的功能。重新导入模块只有实际成本,但是如果您做的是足够的实际工作,则重新导入的成本应该很小。