从使用 pyinstaller 运行单独的 python 脚本的 python 脚本创建 .exe

时间:2021-06-30 20:49:12

标签: python tkinter pyinstaller

简短版本:

我有一系列连接在一起的 python 脚本(一个 .py 关闭并运行一个单独的 .py)。在 VS Code 或 cmd 行中通过终端运行它时,这完全正常。一旦它通过 pyinstaller 在 .exe 中,只有第一个代码有效,并且程序在尝试执行单独的 .py 文件时关闭。

详情:

所有单独的python文件都保存在同一目录中。第一个打开的“Main.py”有一个 tkinter 界面,允许用户选择他们想要运行的 .py 脚本。然后代码关闭主窗口并使用 $ docker run -v pgdata13:/var/lib/postgresql/data postgres:13 PostgreSQL Database directory appears to contain a database; Skipping initialization 2021-06-30 20:48:37.398 UTC [1] LOG: starting PostgreSQL 13.3 (Debian 13.3-1.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit 2021-06-30 20:48:37.398 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432 2021-06-30 20:48:37.398 UTC [1] LOG: listening on IPv6 address "::", port 5432 2021-06-30 20:48:37.400 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432" 2021-06-30 20:48:37.403 UTC [21] LOG: database system was shut down at 2021-06-30 20:46:37 UTC 2021-06-30 20:48:37.406 UTC [1] LOG: database system is ready to accept connections 打开选定的 python 脚本。 (这是初始代码的简化版本,但我遇到了同样的问题)

exec(open('chosen .py').read())

下一个代码是“Main.py”最后运行的“Script1.py”文件。这是在 VS Code 和 cmd 行中运行良好的步骤,但会导致 pyinstaller 中的 .exe 关闭。

import tkinter as tk
from tkinter import ttk
from tkinter.constants import W
from tkinter import messagebox as mb

""" Open a window to select which separate script to run"""

root = tk.Tk()
root.title('Selection Window')
root.geometry('300x200')

frame_1 = tk.LabelFrame(root, text='Choose Program')
frame_1.pack()

# Using this function to update on radio button select
def radio_button_get():
    global program_int
    choice = radio_ID.get()
    if(choice == 1):
        program_int = 1
    elif(choice == 2):
        program_int = 2

# Display confirmation popup
def run_script():
    if(program_int == 1):
        select = mb.askokcancel("Confirm", "Run choice 1?")
        if(select == 1):
            root.destroy()
        else:
            return
    if(program_int == 2):
        select = mb.askokcancel("Confirm", "No selection")
        if(select == 1):
            root.destroy()
        else:
            return

# Create radio buttons to select program 
radio_ID = tk.IntVar()
radio_ID.set(2)
program_int = 2     # Set default selection

choice_1 = tk.Radiobutton(frame_1, text='Execute Script 1', variable=radio_ID, value=1, command=radio_button_get)
choice_1.pack()
no_choice = tk.Radiobutton(frame_1, text='No Selection', variable=radio_ID, value=2, command=radio_button_get)
no_choice.pack()

# Button to run the selected code
run_button = ttk.Button(root, text='Run', command=run_script)
run_button.pack()
root.mainloop()

# Execute the other python script 
if(program_int == 1):
    exec(open('Script1.py').read())

每个独立的 .py 文件之前都已经用 pyinstaller 成功转成 .exe 文件。我用来执行 pyinstaller 的 cmd 行命令是 import tkinter as tk from tkinter import ttk """ Create this programs GUI window""" root = tk.Tk() root.title('Script 1') def run(): root.destroy() label = ttk.Label(root, text='Close to run') label.pack() button = ttk.Button(root, text='Close', command=run) button.pack() root.mainloop() """ Do some code stuff here""" # When above code is done, want to return to the Main.py window exec(open('Main.py').read()) 这成功地在 dist 文件夹中创建了一个 Main.exe,还包括一个 build 文件夹。

我已通读 pyinstallers 文档,但没有找到我认为在这种情况下有用的任何内容。我能找到的最接近的问题是在 .spec 文件选项中将 python 脚本作为模块导入,但由于代码将 python 脚本作为单独的实体执行,我认为这不是解决方案。

问题是脚本的编码方式和相互引用的方式,还是使用 pyinstaller 的安装过程?如果我遗漏了文档中可以解释此问题的内容,请告诉我,我会查看!

非常感谢任何帮助,谢谢

2 个答案:

答案 0 :(得分:0)

我们必须避免使用 .exec 命令。这是hacky但不安全。参考:Running a Python script from another

改为使用 import :

# Execute the other python script 
if(program_int == 1):
    import Script1

这里也是:

# When above code is done, want to return to the Main.py window
import Main

就是这样,现在使用 pyinstaller。


编辑:

为什么 .exe 文件无法执行另一个脚本,以及为什么 exec() 是问题:

根据文档:

<块引用>

Pyinstaller 分析您的代码以发现所有其他模块并 脚本执行所需的库。然后它收集副本 所有这些文件 - 包括活动的 Python 解释器! - 和 将它们与您的脚本放在一个文件夹中,或者可以选择放在一个文件夹中 单个可执行文件。

因此,当 pyinstaller 分析和创建 .exe 文件时,它只执行 exec() 函数(因此在 pyinstaller 运行时不会抛出错误),pyinstaller 不会将其导入或复制到你的.exe。文件,然后在创建 .exe 文件后,在运行时会抛出错误,指出不存在此类脚本文件,因为它从未编译到该 .exe 文件中。

因此,当执行 pyinstaller 时,使用 import 实际上会将脚本作为模块导入,现在您的 .exe 文件将不会出错。

答案 1 :(得分:0)

不是将脚本作为模块导入,而是为了一次又一次地重新执行它们,而是在 Main.py 中导入另一个脚本作为函数

此外,不要破坏您的主根窗口(因为除非您创建新窗口,否则您将无法再次打开它),使用 .withdraw() 隐藏它,然后使用 .deiconify() 显示.


首先,在Script1.py中:

import tkinter as tk
from tkinter import ttk

""" Create this programs GUI window"""
def script1Function(root):      #the main root window is recieved as parameter, since this function is not inside the scope of Main.py's root
    root2 = tk.Tk()             #change the name to root2 to remove any ambiguity
    root2.title('Script 1')

    def run():
        root2.destroy()         #destroy this root2 window
        root.deiconify()        #show the hidden Main root window
    label = ttk.Label(root2, text='Close to run')
    label.pack()
    button = ttk.Button(root2, text='Close', command=run)
    button.pack()

    root2.mainloop()

然后,在 Main.py 中:

import tkinter as tk
from tkinter import ttk
from tkinter.constants import W
from tkinter import messagebox as mb
from Script1 import script1Function         #importing Script1's function


# Execute the other python script
def openScript1():    
    root.withdraw()                         #hide this root window
    script1Function(root)                   #pass root window as parameter, so that Script1 can show root again
    

""" Open a window to select which separate script to run"""

root = tk.Tk()
root.title('Selection Window')
root.geometry('300x200')

frame_1 = tk.LabelFrame(root, text='Choose Program')
frame_1.pack()

# Using this function to update on radio button select
def radio_button_get():
    global program_int
    choice = radio_ID.get()
    if(choice == 1):
        program_int = 1
    elif(choice == 2):
        program_int = 2

# Display confirmation popup
def run_script():
    global program_int                      #you forgot to make it global
    if(program_int == 1):
        select = mb.askokcancel("Confirm", "Run choice 1?")
        if(select == 1):
            openScript1()
        else:
            return
    if(program_int == 2):
        select = mb.askokcancel("Confirm", "No selection")
        if(select == 1):
            root.destroy()
        else:
            return

# Create radio buttons to select program 
radio_ID = tk.IntVar()
radio_ID.set(2)
program_int = 2     # Set default selection

choice_1 = tk.Radiobutton(frame_1, text='Execute Script 1', variable=radio_ID, value=1, command=radio_button_get)
choice_1.pack()
no_choice = tk.Radiobutton(frame_1, text='No Selection', variable=radio_ID, value=2, command=radio_button_get)
no_choice.pack()

# Button to run the selected code
run_button = ttk.Button(root, text='Run', command=run_script)
run_button.pack()
root.mainloop()