【问题标题】:cProfile causes pickling error when running multiprocessing Python codecProfile 在运行多处理 Python 代码时导致酸洗错误
【发布时间】:2019-05-22 06:35:42
【问题描述】:

我有一个 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 上下文之外运行时不会发生此错误,所以这可能是为酸洗添加了额外的约束?

谁能评论为什么会发生这种情况,和/或我可以如何纠正这个问题?

【问题讨论】:

  • FWIW,我唯一遇到的酸洗问题是酸洗功能。
  • 试试this
  • 谢谢,@Darkonaut,我不知道在写这个问题之前我是怎么没有发现自己的。我的问题是关于一个函数而不是一个类,这是该问题所要解决的问题,但也许一个想法是在一个单独的模块文件中创建函数,正如其中一个答案所暗示的那样作为解决方案?我会试一试...
  • 我还没有对此进行测试,但也许您只需创建一个中间 profile.py 就可以摆脱困境,它只是从您的真实目标脚本导入并调用您的 main

标签: python pickle python-multiprocessing cprofile


【解决方案1】:

您遇到的问题是,通过使用-mcProfile,模块__main__cProfile(代码的实际入口点),而不是您的脚本。 cProfile 尝试通过确保当您的脚本运行时将__name__ 视为"__main__" 来解决此问题,因此它知道它是作为脚本运行的,而不是作为模块导入的,而是@ 987654328@ 仍然是 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 不存在。

我不知道有一个好的解决方案。我能想出的最好办法是手动修复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 似乎仍然有效,并且酸洗可以按预期找到您的功能。只有真正的成本是重新导入你的模块,但如果你做了足够多的实际工作,重新导入的成本应该是相当小的。

【讨论】:

  • 出色的答案,@ShadowRanger,你带我去学校了。这或多或少是here所描述的吗?
  • @JamesAdams:是的,那里采用的方法类似;它基本上完全忽略了__main__ 模块,而是将其重新导入为非__main__ 并仅使用适当限定的版本来引用该模块的非__main__ 版本。
  • 我也有同样的问题,但是我的脚本很简单。我有我的类和在同一个 py 文件中调用它的函数。因此,我没有导入“mymodule”。这种解决方案的变体仍然可行吗?
  • @LiamMcIntyre:无论如何,只要导入自己;无论您的脚本被命名为什么,如图所示导入并更新它。
猜你喜欢
  • 2021-04-29
  • 2018-07-26
  • 2015-04-07
  • 1970-01-01
  • 2011-04-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-30
相关资源
最近更新 更多