如何使用pyinstaller

时间:2018-04-12 22:07:18

标签: python python-3.x pyinstaller python-standalone

我对此问题提出了类似的问题: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]

这将继续我的第二,第三和第四个脚本。

在我的spec文件中,我添加了:

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'] ...我无法让它工作。有任何想法吗?我不确定它是否给了我这个错误,因为它是一个子进程。

1 个答案:

答案 0 :(得分:1)

假设您无法重新构建您的应用,因此这不是必需的(例如,使用multiprocessing代替subprocess),有三种解决方案:

  • 确保.exe包含脚本作为(可执行)zipfile - 或者只使用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:我们为每个脚本复制并粘贴了相同的三行代码,我们必须输入三次每个脚本名称。您可以使用__import__(sys.argv[1][2:]).run(sys.argv[2:])之类的内容进行适当的错误处理。
  • 使用argparse代替这个hacky特殊大小写作为第一个参数。如果您已经向脚本发送了非平凡的参数,那么您可能已经在使用argparse或替代方案。
  • 为每个只调用if __name__ == '__main__':的脚本添加run(sys.argv[1:])块,这样在开发过程中您仍然可以直接运行脚本来测试它们。

我没有做任何这些,因为他们模糊了这个琐碎的例子的想法。

1如果您已经完成了文档,那么文档很有用,但作为教程和解释性原理,并非如此。并且试图编写那些辉煌的PyPA人员多年来无法想出的教程......这可能超出了SO答案的范围。