并行:从兄弟文件夹导入Python文件

6
我有一个目录树。
working_dir\
    main.py
my_agent\
    my_worker.py
my_utility\
    my_utils.py

每个文件中的代码如下。
""" main.py """

import os, sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from my_agent.my_worker import MyWorker
import ray

ray.init()
workers = [MyWorker.remote(i) for i in range(10)]
ids = [worker.get_id.remote() for worker in workers]
# print(*ids, sep='\n')
print(*ray.get(ids), sep='\n')

""" worker.py """
from my_utility import my_utils
import ray

@ray.remote
class MyWorker():
    def __init__(self, id):
        self.id = id

    def get_id(self):
        return my_utils.f(self.id)

""" my_utils.py """
def f(id):
    return '{}: Everything is fine...'.format(id)

以下是收到的错误消息的一部分:
回溯(最近的调用最先): 文件“/Users/aptx4869/anaconda3/envs/p35/lib/python3.5/site-packages/ray/function_manager.py”,第616行,在fetch_and_register_actor中: unpickled_class = pickle.loads(pickled_class) 文件“/Users/aptx4869/anaconda3/envs/p35/lib/python3.5/site-packages/ray/cloudpickle/cloudpickle.py”,第894行,在subimport中: import(name) ImportError: No module named 'my_utility' 回溯(最近的调用最先): 文件“main.py”,第12行,在 print(*ray.get(ids), sep='\n') 文件“/Users/aptx4869/anaconda3/envs/p35/lib/python3.5/site-packages/ray/worker.py”,第2377行,在get中: raise value ray.worker.RayTaskError: ray_worker (pid=30025, host=AiMacbook) 异常:未能导入名称为MyWorker的actor,因此无法执行此方法
如果我删除所有与ray相关的语句,则上述代码可以正常工作。 因此,我大胆猜测原因是ray在新进程中运行每个actor,而sys.path.append仅在主进程中起作用。 因此,我将以下代码添加到worker.py中:
import os, sys
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

但它仍然无法工作:相同的错误消息出现。我已经没有主意了,我该怎么办?

我认为 os.path.dirname(os.path.dirname(__file__))os.path.join(os.path.dirname(__file__), '..') 更好。 - spaniard
你尝试过删除这行代码 if __name__ == '__main__' and __package__ is None: 吗?并且总是将父目录添加到你的路径中。 - spaniard
@spaniard 是的,同样的故事继续发生。 - Maybe
@Darkonaut 是的,我已经更改了所有相对名称... 错误发生在 print 语句处。 在我看来,如果错误是由名称冲突引起的,那么它不应该发生得这么晚。 - Maybe
我不能代表ray说话,但至少在标准库的multiprocessing.Pool中,对于每个分布式任务,使用的函数必须再次进行导入。我不熟悉ray的内部结构,但是那行ImportError: No module named 'utils'让我感到好奇。看起来它正在尝试从utils.py而不是从目录中导入utils。 - Darkonaut
显示剩余7条评论
2个回答

10

您对问题的理解是正确的。

在您的示例中,您修改了main.py中的sys.path,以便能够导入my_agent.my_workermy_utility.my_utils

然而,这个路径的更改没有传播到工作进程中,因此如果您运行像下面这样的远程函数:

@ray.remote
def f():
    # Print the PYTHONPATH on the worker process.
    import sys
    print(sys.path)

f.remote()

您会发现,工作进程上的sys.path不包括您添加的父目录。
修改工作进程上的sys.path(例如在MyWorker构造函数中)不起作用的原因是,MyWorker类定义被pickled并发送到工作进程。然后,工作进程对其进行反pickle,而反pickle类定义的过程需要导入my_utils,但由于actor构造函数尚未运行,因此导入失败。
这里有几个可能的解决方案。
  1. Run the script with something like

    PYTHONPATH=$(dirname $(pwd)):$PYTHONPATH python main.py
    

    (from within working_dir/). That should solve the issue because in this case the worker processes are forked from the scheduler process (which is forked from the main Python interpreter when you call ray.init() and so the environment variable will be inherited by the workers (this doesn't happen for sys.path presumably because it is not an environment variable).

  2. It looks like adding the line

    parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    os.environ["PYTHONPATH"] = parent_dir + ":" + os.environ.get("PYTHONPATH", "")
    

    in main.py (before the ray.init() call) also works for the same reason as above.

  3. Consider adding a setup.py and installing your project as a Python package so that it's automatically on the relevant path.


3

如果您有多个依赖项,请使用“py_modules”:“[路径/项目名称/模块]”。 - han14466

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接