如何使用 pyinstaller 将多个子进程 python 文件编译成单个 .exe 文件
Posted
技术标签:
【中文标题】如何使用 pyinstaller 将多个子进程 python 文件编译成单个 .exe 文件【英文标题】:How to compile multiple subprocess python files into single .exe file using pyinstaller 【发布时间】:2018-04-12 22:07:17 【问题描述】:我有一个类似的问题:Similar Question。 我有一个 GUI,用户可以在其中输入信息,其他脚本使用其中的一些信息来运行。每个按钮有 4 个不同的脚本。我将它们作为子进程运行,这样主 gui 就不会出现或说它没有响应。这是我所拥有的一个示例,因为自从我使用 PAGE 生成 gui 以来,代码真的很长。
###Main.py#####
import subprocess
def resource_path(relative_path):
#I got this from another post to include images but I'm also using it to include the scripts"
try:
# PyInstaller creates a temp folder and stores path in _MEIPASS
base_path = sys._MEIPASS
except Exception:
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
Class aclass:
def get_info(self):
global ModelNumber, Serial,SpecFile,dateprint,Oper,outputfolder
ModelNumber=self.Model.get()
Serial=self.SerialNumber.get()
outputfolder=self.TEntry2.get()
SpecFile= self.Spec_File.get()
return ModelNumber,Serial,SpecFile,outputfolder
def First(self):
aclass.get_info(self) #Where I use the resource path function
First_proc = subprocess.Popen([sys.executable, resource_path('first.py'),str(ModelNumber),str(Serial),str(path),str(outputfolder)])
First_proc.wait()
#####First.py#####
import numpy as np
import scipy
from main import aclass
ModelNumber = sys.argv[1]
Serial = sys.argv[2]
path = sys.argv[3]
path_save = sys.argv[4]
这适用于我的第二个、第三个和第四个脚本。
在我的规范文件中,我添加了:
a.datas +=[('first.py','C\\path\\to\\script\\first.py','DATA')]
a.datas +=[('main.py','C\\path\\to\\script\\main.py','DATA')]
这可以编译并且可以工作,但是当我尝试将其转换为 .exe 时,它会崩溃,因为它无法正确导入 first.py 及其自己的库(numpy、scipy....等)。我已经尝试将它添加到 a.datas 和规范文件中的 runtime_hooks=['first.py'] ......但我无法让它工作。有任何想法吗?我不确定它是否给了我这个错误,因为它是一个子进程。
【问题讨论】:
为什么不将 first.py 也构建成一个 exe,然后使用子进程打开该 exe?捆绑一个然后调用 python 的 exe 似乎是一种破碎的方式(另外,我发现捆绑路径与 dist 路径不一样是有问题的)(是a.datas += [('assets/my_file.txt','assets/my_file.txt','DATA'),]
)
如果你真的想这样做,最简单的方法可能是创建一个 entrypoint
脚本,读取 sys.argv[1]
并将其发送到 import
一个不同的模块(main
,或first
或 second
等)并从中调用一个方法。然后,main
不会尝试对first.py
进行子处理,而是使用first
作为额外参数对entrypoint
进行子处理。
@abarnert 你有一个入口点脚本可能是什么样子的例子吗?我只是制作基本的脚本。没什么太高级的
@MoeAvera 我试图在我的答案中展示如何做到这一点而没有任何太高级的东西 - 它增加了 bit 更多的工作,但与尝试教你高级@987654335相比@ features,我认为这为我们双方节省了很多时间。 :)
【参考方案1】:
假设您无法重组您的应用,因此这不是必需的(例如,使用 multiprocessing
而不是 subprocess
),有以下三种解决方案:
pkg_resources
)并将脚本复制到一个临时目录,以便您可以从那里运行它。
编写一个多入口点包装脚本,它可以作为主程序运行,也可以作为每个脚本运行 - 因为虽然您不能从打包的 exe 中运行脚本,但您可以从其中导入一个模块。
再次使用pkg_resources
,编写一个运行脚本的包装器,将其加载为字符串并改为使用exec
运行它。
第二个可能是最干净的,但它是有点工作。而且,虽然我们可以依靠 setuptools
entrypoints 来完成某些工作,但尝试解释如何做到这一点比解释如何手动完成要困难得多,1 所以我要后者。
假设您的代码如下所示:
# main.py
import subprocess
import sys
spam, eggs = sys.argv[1], sys.argv[2]
subprocess.run([sys.executable, 'vikings.py', spam])
subprocess.run([sys.executable, 'waitress.py', spam, eggs])
# vikings.py
import sys
print(' '.join(['spam'] * int(sys.argv[1])))
# waitress.py
import sys
import time
spam, eggs = int(sys.argv[1]), int(sys.argv[2]))
if eggs > spam:
print("You can't have more eggs than spam!")
sys.exit(2)
print("Frying...")
time.sleep(2)
raise Exception("This sketch is getting too silly!")
所以,你可以这样运行:
$ python3 main.py 3 4
spam spam spam
You can't have more eggs than spam!
我们想要重新组织它,以便有一个脚本可以查看命令行参数来决定导入什么。这是最小的改变:
# main.py
import subprocess
import sys
if sys.argv[1][:2] == '--':
script = sys.argv[1][2:]
if script == 'vikings':
import vikings
vikings.run(*sys.argv[2:])
elif script == 'waitress':
import waitress
waitress.run(*sys.argv[2:])
else:
raise Exception(f'Unknown script script')
else:
spam, eggs = sys.argv[1], sys.argv[2]
subprocess.run([sys.executable, __file__, '--vikings', spam])
subprocess.run([sys.executable, __file__, '--waitress', spam, eggs])
# vikings.py
def run(spam):
print(' '.join(['spam'] * int(spam)))
# waitress.py
import sys
import time
def run(spam, eggs):
spam, eggs = int(spam), int(eggs)
if eggs > spam:
print("You can't have more eggs than spam!")
sys.exit(2)
print("Frying...")
time.sleep(2)
raise Exception("This sketch is getting too silly!")
现在:
$ python3 main.py 3 4
spam spam spam
You can't have more eggs than spam!
在现实生活中可能需要考虑的一些变化:
DRY:我们为每个脚本复制并粘贴了相同的三行代码,我们必须将每个脚本名称键入 3 次。您可以使用__import__(sys.argv[1][2:]).run(sys.argv[2:])
之类的内容并进行适当的错误处理。
在第一个参数中使用argparse
而不是这个hacky 的特殊大小写。如果您已经向脚本发送了重要的参数,那么您可能已经在使用argparse
或其他替代方法。
为每个只调用run(sys.argv[1:])
的脚本添加一个if __name__ == '__main__':
块,以便在开发过程中您仍然可以直接运行脚本来测试它们。
我没有做这些,因为它们会掩盖这个琐碎示例的想法。
1 如果您已经完成了该文档,那么它可以作为复习,但作为教程和解释性原理,并没有那么多。并尝试编写出色的 PyPA 人员多年来无法提出的教程……这可能超出了 SO 答案的范围。
【讨论】:
非常感谢!这绝对是一次学习体验。以上是关于如何使用 pyinstaller 将多个子进程 python 文件编译成单个 .exe 文件的主要内容,如果未能解决你的问题,请参考以下文章
子进程似乎在 pyinstaller exe 文件中不起作用