在 Python 多处理中使用 __name__=='__main__' 的解决方法
Workaround for using __name__=='__main__' in Python multiprocessing
我们都知道我们需要保护 main()
when 运行ning code with multiprocessing
in Python using if __name__ == '__main__'
.
我知道在某些情况下这是必要的,以便访问 main 中定义的函数,但我不明白为什么在这种情况下这是必要的:
file2.py
import numpy as np
from multiprocessing import Pool
class Something(object):
def get_image(self):
return np.random.rand(64,64)
def mp(self):
image = self.get_image()
p = Pool(2)
res1 = p.apply_async(np.sum, (image,))
res2 = p.apply_async(np.mean, (image,))
print(res1.get())
print(res2.get())
p.close()
p.join()
main.py
from file2 import Something
s = Something()
s.mp()
Something
工作所需的所有函数或导入都是 file2.py
的一部分。为什么subprocess需要重新运行 the main.py
?
我认为 __name__
解决方案不是很好,因为这阻止了我分发 file2.py
的代码,因为我无法确保他们保护了他们的主要。
Windows 没有解决方法吗?
包是如何解决这个问题的(因为我从来没有遇到过任何没有用任何包保护我的主程序的问题——它们只是没有使用多处理吗?)
编辑:
我知道这是因为 fork()
没有在 Windows 中实现。我只是问是否有黑客让解释器从 file2.py
而不是 main.py
开始,因为我可以确定 file2.py
是自给自足的
使用 "spawn" 启动方法时,新进程是 Python 从头开始的解释器。子进程中的新 Python 解释器无法确定需要导入哪些模块,因此它们再次导入主模块,而主模块又将导入其他所有模块。这意味着必须可以在没有任何副作用的情况下导入主模块。
如果您使用的平台与Windows不同,您可以改用"fork"启动方式,就不会出现这个问题。
也就是说,使用 if __name__ == "__main__":
有什么问题?它有很多额外的好处,例如文档工具将能够处理你的主模块,单元测试更容易等,所以你应该在任何情况下使用它。
windows 需要 if __name__ == '__main__'
,因为 windows 没有进程的 "fork" 选项。
例如,在linux中,您可以fork
进程,这样父进程将被复制,副本将成为子进程(并且它可以访问已经导入的您在父进程中加载的代码)
由于您不能在 windows 中分叉,python 只需在子进程中导入父进程导入的所有代码。这会产生类似的效果,但如果您不使用 __name__
技巧,此导入将在子进程中再次执行您的代码(这将使其创建自己的子进程,依此类推)。
所以即使在您的示例中 main.py
也会被再次导入(因为所有文件都被再次导入)。 python 无法猜测子进程应该导入什么特定的 python 脚本。
仅供参考,您还应该注意其他限制,例如使用全局变量,您可以在此处阅读相关内容 https://docs.python.org/2/library/multiprocessing.html#windows
主模块已导入(但带有 __name__ != '__main__'
,因为 Windows 试图在没有分叉的系统上模拟类似分叉的行为)。 multiprocessing
无法知道您在主模块中没有做任何重要的事情,因此完成导入 "just in case" 以创建一个类似于主进程中的环境。如果它不这样做,则在 main 中因副作用而发生的各种事情(例如导入、具有持久副作用的配置调用等)可能无法在子进程中正确执行。
因此,如果他们不保护自己的 __main__
,代码就不是多处理安全的(单元测试、导入安全等也不安全)。 if __name__ == '__main__':
保护包装应该是所有正确主模块的一部分。继续分发它,并附上关于需要多处理安全主模块保护的注释。
正如其他人提到的,Windows 上的 spawn()
方法将为每个解释器实例重新导入代码。此导入将在子进程中再次执行您的代码(这将使它创建自己的子进程,依此类推)。
解决方法是将多处理脚本拉到一个单独的文件中,然后使用子进程从主脚本启动它。
我通过在临时目录中 pickle 将变量传递到脚本中,然后使用 argparse 将临时目录传递到子进程中。
然后我将结果腌制到临时目录中,主脚本会在其中检索它们。
这是我编写的 file_hasher()
函数示例:
main_program.py
import os, pickle, shutil, subprocess, sys, tempfile
def file_hasher(filenames):
try:
subprocess_directory = tempfile.mkdtemp()
input_arguments_file = os.path.join(subprocess_directory, 'input_arguments.dat')
with open(input_arguments_file, 'wb') as func_inputs:
pickle.dump(filenames, func_inputs)
current_path = os.path.dirname(os.path.realpath(__file__))
file_hasher = os.path.join(current_path, 'file_hasher.py')
python_interpreter = sys.executable
proc = subprocess.call([python_interpreter, file_hasher, subprocess_directory],
timeout=60,
)
output_file = os.path.join(subprocess_directory, 'function_outputs.dat')
with open(output_file, 'rb') as func_outputs:
hashlist = pickle.load(func_outputs)
finally:
shutil.rmtree(subprocess_directory)
return hashlist
file_hasher.py
#! /usr/bin/env python
import argparse, hashlib, os, pickle
from multiprocessing import Pool
def file_hasher(input_file):
with open(input_file, 'rb') as f:
data = f.read()
md5_hash = hashlib.md5(data)
hashval = md5_hash.hexdigest()
return hashval
if __name__=='__main__':
argument_parser = argparse.ArgumentParser()
argument_parser.add_argument('subprocess_directory', type=str)
subprocess_directory = argument_parser.parse_args().subprocess_directory
arguments_file = os.path.join(subprocess_directory, 'input_arguments.dat')
with open(arguments_file, 'rb') as func_inputs:
filenames = pickle.load(func_inputs)
hashlist = []
p = Pool()
for r in p.imap(file_hasher, filenames):
hashlist.append(r)
output_file = os.path.join(subprocess_directory, 'function_outputs.dat')
with open(output_file, 'wb') as func_outputs:
pickle.dump(hashlist, func_outputs)
一定有更好的方法...
我们都知道我们需要保护 main()
when 运行ning code with multiprocessing
in Python using if __name__ == '__main__'
.
我知道在某些情况下这是必要的,以便访问 main 中定义的函数,但我不明白为什么在这种情况下这是必要的:
file2.py
import numpy as np
from multiprocessing import Pool
class Something(object):
def get_image(self):
return np.random.rand(64,64)
def mp(self):
image = self.get_image()
p = Pool(2)
res1 = p.apply_async(np.sum, (image,))
res2 = p.apply_async(np.mean, (image,))
print(res1.get())
print(res2.get())
p.close()
p.join()
main.py
from file2 import Something
s = Something()
s.mp()
Something
工作所需的所有函数或导入都是 file2.py
的一部分。为什么subprocess需要重新运行 the main.py
?
我认为 __name__
解决方案不是很好,因为这阻止了我分发 file2.py
的代码,因为我无法确保他们保护了他们的主要。
Windows 没有解决方法吗?
包是如何解决这个问题的(因为我从来没有遇到过任何没有用任何包保护我的主程序的问题——它们只是没有使用多处理吗?)
编辑:
我知道这是因为 fork()
没有在 Windows 中实现。我只是问是否有黑客让解释器从 file2.py
而不是 main.py
开始,因为我可以确定 file2.py
是自给自足的
使用 "spawn" 启动方法时,新进程是 Python 从头开始的解释器。子进程中的新 Python 解释器无法确定需要导入哪些模块,因此它们再次导入主模块,而主模块又将导入其他所有模块。这意味着必须可以在没有任何副作用的情况下导入主模块。
如果您使用的平台与Windows不同,您可以改用"fork"启动方式,就不会出现这个问题。
也就是说,使用 if __name__ == "__main__":
有什么问题?它有很多额外的好处,例如文档工具将能够处理你的主模块,单元测试更容易等,所以你应该在任何情况下使用它。
windows 需要 if __name__ == '__main__'
,因为 windows 没有进程的 "fork" 选项。
例如,在linux中,您可以fork
进程,这样父进程将被复制,副本将成为子进程(并且它可以访问已经导入的您在父进程中加载的代码)
由于您不能在 windows 中分叉,python 只需在子进程中导入父进程导入的所有代码。这会产生类似的效果,但如果您不使用 __name__
技巧,此导入将在子进程中再次执行您的代码(这将使其创建自己的子进程,依此类推)。
所以即使在您的示例中 main.py
也会被再次导入(因为所有文件都被再次导入)。 python 无法猜测子进程应该导入什么特定的 python 脚本。
仅供参考,您还应该注意其他限制,例如使用全局变量,您可以在此处阅读相关内容 https://docs.python.org/2/library/multiprocessing.html#windows
主模块已导入(但带有 __name__ != '__main__'
,因为 Windows 试图在没有分叉的系统上模拟类似分叉的行为)。 multiprocessing
无法知道您在主模块中没有做任何重要的事情,因此完成导入 "just in case" 以创建一个类似于主进程中的环境。如果它不这样做,则在 main 中因副作用而发生的各种事情(例如导入、具有持久副作用的配置调用等)可能无法在子进程中正确执行。
因此,如果他们不保护自己的 __main__
,代码就不是多处理安全的(单元测试、导入安全等也不安全)。 if __name__ == '__main__':
保护包装应该是所有正确主模块的一部分。继续分发它,并附上关于需要多处理安全主模块保护的注释。
正如其他人提到的,Windows 上的 spawn()
方法将为每个解释器实例重新导入代码。此导入将在子进程中再次执行您的代码(这将使它创建自己的子进程,依此类推)。
解决方法是将多处理脚本拉到一个单独的文件中,然后使用子进程从主脚本启动它。
我通过在临时目录中 pickle 将变量传递到脚本中,然后使用 argparse 将临时目录传递到子进程中。
然后我将结果腌制到临时目录中,主脚本会在其中检索它们。
这是我编写的 file_hasher()
函数示例:
main_program.py
import os, pickle, shutil, subprocess, sys, tempfile
def file_hasher(filenames):
try:
subprocess_directory = tempfile.mkdtemp()
input_arguments_file = os.path.join(subprocess_directory, 'input_arguments.dat')
with open(input_arguments_file, 'wb') as func_inputs:
pickle.dump(filenames, func_inputs)
current_path = os.path.dirname(os.path.realpath(__file__))
file_hasher = os.path.join(current_path, 'file_hasher.py')
python_interpreter = sys.executable
proc = subprocess.call([python_interpreter, file_hasher, subprocess_directory],
timeout=60,
)
output_file = os.path.join(subprocess_directory, 'function_outputs.dat')
with open(output_file, 'rb') as func_outputs:
hashlist = pickle.load(func_outputs)
finally:
shutil.rmtree(subprocess_directory)
return hashlist
file_hasher.py
#! /usr/bin/env python
import argparse, hashlib, os, pickle
from multiprocessing import Pool
def file_hasher(input_file):
with open(input_file, 'rb') as f:
data = f.read()
md5_hash = hashlib.md5(data)
hashval = md5_hash.hexdigest()
return hashval
if __name__=='__main__':
argument_parser = argparse.ArgumentParser()
argument_parser.add_argument('subprocess_directory', type=str)
subprocess_directory = argument_parser.parse_args().subprocess_directory
arguments_file = os.path.join(subprocess_directory, 'input_arguments.dat')
with open(arguments_file, 'rb') as func_inputs:
filenames = pickle.load(func_inputs)
hashlist = []
p = Pool()
for r in p.imap(file_hasher, filenames):
hashlist.append(r)
output_file = os.path.join(subprocess_directory, 'function_outputs.dat')
with open(output_file, 'wb') as func_outputs:
pickle.dump(hashlist, func_outputs)
一定有更好的方法...