subprocess not working after onefile pyinstaller even with stdout/stdin defined

3,956 views
Skip to first unread message

Matthew Lawrence

unread,
Jun 22, 2021, 4:55:24 PM6/22/21
to PyInstaller
Hi - I posted this question to Stack Overflow (https://stackoverflow.com/questions/68042440/subprocess-not-working-after-onefile-pyinstaller-even-with-stdout-stdin-defined), but haven't had a response, so thought I'd try here.  Apologies if this is not the right forum.

I have a tkinter GUI with a text box and run button. Pressing the run button turns it to yellow and starts a subroutine that prints a few numbers. Text output from the subroutine is redirected to the GUI text box. However, after creating a standalone executable file with pyinstaller, it no longer works. Pressing the run button doesn't seem to start the subprocess. It does turn yellow, but no text appears in the text box and it seems to start another instance of the main program - a second GUI appears after about 10 seconds which is how long it takes for the initial GUI to appear. The run button stays yellow on the initial GUI.

I've seen a bit online about other people having issues with subprocesses not running after pyinstaller, but most of the solutions seem to be to make sure stdout, stdin are set to subprocess.PIPE which I have, so I'm at a bit of a loss what to try next.

When running the resulting executable from Anaconda prompt, I also get no error messages or anything useful to help debug.

I'm creating my standalone with this:

pyinstaller --onefile --add-data "testsubprocess.py;." simpleGUI.py

I've also tried without  --add-data "testsubprocess.py;.", making sure my executable and testsubprocess.py are in the same folder.

My subprocess file, testsubprocess.py is:

import time

for i in range(3):
    print("%d.%d" % divmod(i, 10))
    time.sleep(0.5)


My main GUI file, simpleGUI.py, is:

import sys
import subprocess
from threading import Thread
import tkinter as tk
from queue import Queue, Empty


def iter_except(function, exception):
    try:
        while True:
            yield function()
    except exception:
        return


class DisplaySubprocessOutputDemo:
    def __init__(self, root):
        self.root = root

        width=600
        height=350
        xloc=0
        yloc=10
        self.root.geometry('%dx%d+%d+%d' % (width, height, xloc, yloc))

        self.statustext = tk.Text(self.root, height=4, width=30)
        self.statustext.grid(row=3, column=1)

        self.startbutton = tk.Button(self.root, text = 'Start', command=self.startprocess, bg='green', activebackground = 'orange')
        self.startbutton.config(height = 2, width = 15)
        self.startbutton.grid(row = 5, column=0,sticky='E')
        self.startbuttonpresses = 0
        
        exitbutton = tk.Button(self.root, text = 'Exit', command=self.quit, bg='red')
        exitbutton.config(height = 2, width = 15)
        exitbutton.grid(row = 5, column=4, sticky='E')
        
        
    def startprocess(self):    
        self.startbuttonpresses = self.startbuttonpresses+1
        
        if self.startbuttonpresses == 1:
            
            self.startbutton.configure(bg='yellow')
            self.startbutton.configure(text='Stop')
         
            self.process = subprocess.Popen([sys.executable, "-u", "testsubprocess.py"], shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT)
    
            q = Queue(maxsize=1024)  
            t = Thread(target=self.reader_thread, args=[q])
            t.daemon = True 
            t.start()
    
            self.updatetext(q) 
            
        else:
            self.startbuttonpresses = 0
            self.process.kill()
            self.startbutton.configure(bg='green')
            self.startbutton.configure(text='Start')
            

    def reader_thread(self, q):
        try:
            with self.process.stdout as pipe:
                for line in iter(pipe.readline, b''):
                    q.put(line)
        finally:
            q.put(None)


    def updatetext(self, q):
        for line in iter_except(q.get_nowait, Empty): # display all content
            if line is None:
                self.startbuttonpresses = 0
                self.startbutton.configure(bg='green')
                self.startbutton.configure(text='Start')
                
                return
            else:
                self.statustext.insert(tk.END, line)

        self.root.after(400, self.updatetext, q) 


    def quit(self):
        try:
            self.process.kill() 
        except Exception:
            pass
        self.root.destroy()


root = tk.Tk()
app = DisplaySubprocessOutputDemo(root)
root.protocol("WM_DELETE_WINDOW", app.quit)
root.eval('tk::PlaceWindow %s center' % root.winfo_pathname(root.winfo_id()))
root.mainloop()

Steve Barnes

unread,
Jun 23, 2021, 2:40:58 AM6/23/21
to pyins...@googlegroups.com

A couple of things occur to me on looking at your code:

 

  1. You have not if __name__ == "__main__": in your file - this is a great pattern to follow.
  2. You would probably be better off using the multiprocessing library rather than subprocess

When you code: self.process = subprocess.Popen([sys.executable, "-u", "testsubprocess.py"], shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT) is executed directly as python simpleGUI.py then sys.executable will be /path/to/python.exe but when you execute your simpleGUI.exe then it will be simpleGUI.exe this is why you get a new instance!

 

Hope that helps!

 

Steve

--
You received this message because you are subscribed to the Google Groups "PyInstaller" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pyinstaller...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/pyinstaller/127a328b-e2c9-4d08-8a71-f574b0c18420n%40googlegroups.com.

bwoodsend

unread,
Jun 23, 2021, 3:30:12 PM6/23/21
to PyInstaller

subprocess.Popen([sys.executable, “-u”, “testsubprocess.py”],

A frozen application does not ship a Python interpreter. Instead sys.executable points to your EXE file which is equivalent to python testsubprocess.py so what you’re effectively running is subprocess.Popen(["python", "testsubprocess.py", "-u", "testsubprocess.py"], which will result in an infinite loop of subprocesses with every process invoking another child process. As Steve says, use the multiprocessing module and make sure you use the freeze_support() function.

And for the reference, the issue with having to assign all 3 stdin, stdout, stderr is only applicable to --windowed mode and simply results in an exception of you don’t do it.

Matthew Lawrence

unread,
Jun 23, 2021, 3:30:30 PM6/23/21
to PyInstaller
Thank you - that is helpful.

I'm sure it's not recommended, but I can get everything to work now if I replace sys.executable in the code with the path to my python.exe in quotes.

Matthew

Steve Barnes

unread,
Jun 24, 2021, 1:46:35 AM6/24/21
to pyins...@googlegroups.com

Hi Matthew,

 

That will work on your machine at least as long as you don't change where python is installed but not on the machine of anybody else who doesn't have python installed in exactly the same place - since you are using pyinstaller I would guess that this is not what you would want.

Reply all
Reply to author
Forward
0 new messages