我需要用python3.8执行一个python函数,并且我正在python3.9上运行我的主程序。
我的主程序如下:
import multiprocessing as mp
def run(function, executable):
mp.set_start_method("spawn", force=True)
mp.set_executable(executable)
p = mp.Process(target=function)
p.start()
if __name__ == "__main__":
from module import foo
run(
function=foo.bar,
executable="/bin/python3.8",
)
foo模块的bar函数如下所示:
import pathlib
def bar():
"""Do some python3.8 here"""
在导入由python3.8执行的代码中的包时(通过打印sys.exible进行检查(,似乎是从python3.9导入包。在我的案例中,我在pathlib包中遇到了这个问题,但numpy:也出现了同样的问题
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "/usr/lib/python3.8/multiprocessing/spawn.py", line 116, in spawn_main
exitcode = _main(fd, parent_sentinel)
File "/usr/lib/python3.8/multiprocessing/spawn.py", line 126, in _main
self = reduction.pickle.load(from_parent)
File "/gitrepos/mp-issue/module/foo.py", line 1, in <module>
import pathlib
File "/usr/lib/python3.9/pathlib.py", line 13, in <module>
from urllib.parse import quote_from_bytes as urlquote_from_bytes
File "/usr/lib/python3.9/urllib/parse.py", line 147, in <module>
class _NetlocResultMixinBase(object):
File "/usr/lib/python3.9/urllib/parse.py", line 183, in _NetlocResultMixinBase
__class_getitem__ = classmethod(types.GenericAlias)
AttributeError: module 'types' has no attribute 'GenericAlias'
我想我会遇到这个问题,因为所有3.9包都与3.8不兼容。我尝试在bar函数中移动导入,但错误仍然发生。
我是滥用了多处理,还是应该用其他方式?
我首先要确保模块foo
可用于Python 3.8,如果它当前是从本地目录加载的模块(即未与pip
一起安装(,则应该是这种情况。否则,您将需要在Python 3.8中安装foo
。如果当前加载foo
取决于正在设置的环境变量PYTHONPATH,则需要通过将变量MUST_PASS_PYTHONPATH
设置为True
:来传递当前环境
if __name__ == "__main__":
import subprocess
MUST_PASS_PYTHONPATH = False
# pass current environment in case PYTHONPATH is required
d = dict(os.environ) if MUST_PASS_PYTHONPATH else None
subprocess.run(['/bin/python3.8', '-c', 'from module import foo; foo()'], env=d)
之所以会发生这种情况,是因为sys.path
没有针对子进程进行更改。您需要将安装python3.8的库(内置或其他(的路径添加到子进程的sys.path
中。您可以制作一个decorator,每当调用函数时,它都会自动执行此操作,但您需要将您所做的每一次导入都包含在计划使用多处理调用的每个函数中。此外,decorator是工厂函数,不能在标准库中使用multiprocessing
进行pickle(必须使用标准库外的multiprocess
才能做到这一点(。
一个更好的解决方案可能是在导入模块foo
时添加路径。这意味着sys.path
也将针对您的主进程进行更改,但有一种变通方法,包括让您的模块知道您将从哪个可执行文件调用它。这方面的一般解决方案可能容易出错,而且实现起来相当棘手(例如,您可以设置添加自己的sys.path
的虚拟环境(。话虽如此,这里有一些你可以做的事情:
内部foo.py
import os
import sys
def prepare():
dir_name = os.path.dirname(sys.executable)
to_add = [None, 'lib', 'DLLs', f'python{sys.version_info.major}{sys.version_info.minor}.zip']
for name in to_add:
if name is None:
sys.path.insert(0, dir_name)
continue
sys.path.insert(0, os.path.join(dir_name, name))
if sys.executable == r"pathtopython.exe":
prepare()
# Your imports come here, make sure to import os and sys again!
import pathlib, sys, os
def bar():
print('ok')
确保将path/to/python.exe
编辑到实际的可执行文件位置。这是因为与sys.path
不同,sys.exectuable
在使用set_executable_location
函数生成子代时会通过多处理进行更改。
您应该接受的一般想法是,如果您要更改解释器的python版本,您需要更改子进程的sys.path
,以便它们指向必须从何处导入库。多处理器不会为你做到这一点。