简体   繁体   中英

How can I add a path to the QProcess PATH environment variable? (PyQt5 on Python 3.7)

1. The problem explained

I instantiate a QProcess() -object just before the application shows its main window. The QProcess() -instance is stored in the self.__myProcess variable, and stays alive as long as you can see the main window.

The main window looks like this:

在此处输入图片说明

When you click on the button, the following code executes:

def __btn_clicked(self):
    self.__add_openocd_to_env()
    command = "openocd.exe" + '\r\n'
    self.__myProcess.start(command)

The last two lines are quite clear: the command openocd.exe is passed to self.__myProcess and executes. What this executable actually does is not important here. In fact, I could use any random executable. The point is: if the executable is in my Windows PATH environment variable, it gets found and executed.

Imagine the executable is NOT in the PATH environment variable. Then the function self.__add_openocd_to_env() should fix that issue:

def __add_openocd_to_env(self):
    env = self.__myProcess.processEnvironment()
    env.insert("PATH", "C:\\Users\\Kristof\\programs\\openocd_0.10.0\\bin;" + env.value("PATH"))
    self.__myProcess.setProcessEnvironment(env)

However, I've noticed it has no effect at all. I have tried a lot of different things in this function, but it just won't have any effect.


You can find the full code here:
If you have Python 3 installed with PyQt5, you can simply copy-paste the code into a .py module and run it. You should see the little window with the pushbutton. Of course you should change the path "C:\\Users\\Kristof.." to something valid on your computer. You can choose any executable you like for this test.

import sys
import os
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

class CustomMainWindow(QMainWindow):
    def __init__(self):
        super(CustomMainWindow, self).__init__()
        # -------------------------------- #
        #          QProcess() setup        #
        # -------------------------------- #
        self.__myProcess = QProcess()
        self.__myProcess.setProcessChannelMode(QProcess.MergedChannels)
        self.__myProcess.readyRead.connect(self.__on_output)
        self.__myProcess.errorOccurred.connect(self.__on_error)
        self.__myProcess.finished.connect(self.__on_exit)

        # -------------------------------- #
        #           Window setup           #
        # -------------------------------- #
        self.setGeometry(100, 100, 800, 200)
        self.setWindowTitle("QProcess test")

        self.__frm = QFrame(self)
        self.__frm.setStyleSheet("QWidget { background-color: #ffffff }")
        self.__lyt = QVBoxLayout()
        self.__lyt.setAlignment(Qt.AlignTop)
        self.__frm.setLayout(self.__lyt)
        self.setCentralWidget(self.__frm)

        self.__myBtn = QPushButton("START QPROCESS()")
        self.__myBtn.clicked.connect(self.__btn_clicked)
        self.__myBtn.setFixedHeight(70)
        self.__myBtn.setFixedWidth(200)
        self.__lyt.addWidget(self.__myBtn)
        self.show()

    def __add_openocd_to_env(self):
        env = self.__myProcess.processEnvironment()
        env.insert("PATH", "C:\\Users\\Kristof\\programs\\openocd_0.10.0\\bin;" + env.value("PATH"))
        self.__myProcess.setProcessEnvironment(env)

    def __btn_clicked(self):
        self.__add_openocd_to_env()
        command = "openocd.exe" + '\r\n'
        self.__myProcess.start(command)

    def __on_output(self):
        data = bytes(self.__myProcess.readAll()).decode().replace('\r\n', '\n')
        print(data)

    def __on_error(self, error):
        print("")
        print("Process error: {0}".format(str(error)))
        print("")


    def __on_exit(self, exitCode, exitStatus):
        print("")
        print("ExitCode = {0}".format(str(exitCode)))
        print("ExitStatus = {0}".format(str(exitStatus)))
        print("")


if __name__ == '__main__':
    app = QApplication(sys.argv)
    QApplication.setStyle(QStyleFactory.create('Fusion'))
    myGUI = CustomMainWindow()
    sys.exit(app.exec_())


2. My question

I know I could simply add "C:\\Users\\Kristof\\programs\\openocd_0.10.0\\bin" to my Windows PATH environment variable before instantiating the QProcess() . But that's not the point. I want to know how to add it to the PATH environment variable for that one specific QProcess() -instance. If possible, it should not affect any other QProcess() -instances around in my software, nor should it affect any future QProcess() -instances I create later on.

3. System settings

I use the PyQt5 framework in Python 3.7 on Windows 10.



I've just tried to improve the QProcess() setup in the following way:

        # -------------------------------- #
        #          QProcess() setup        #
        # -------------------------------- #
        self.__myProcess = QProcess()
        self.__myProcess.setProcessChannelMode(QProcess.MergedChannels)
        self.__myProcess.readyRead.connect(self.__on_output)
        self.__myProcess.errorOccurred.connect(self.__on_error)
        self.__myProcess.finished.connect(self.__on_exit)

        # NEW: initialize the environment variables for self.__myProcess:
        env = QProcessEnvironment.systemEnvironment()
        self.__myProcess.setProcessEnvironment(env)

I was hopefull ... but it still won't work :-(

Based on the comment of Mr. @JonBrave, I have written the following workaround:

import sys
import os
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

class CustomMainWindow(QMainWindow):
    def __init__(self):
        super(CustomMainWindow, self).__init__()
        # -------------------------------- #
        #          QProcess() setup        #
        # -------------------------------- #
        self.__myProcess = QProcess()
        self.__myProcess.setProcessChannelMode(QProcess.MergedChannels)
        self.__myProcess.readyRead.connect(self.__on_output)
        self.__myProcess.errorOccurred.connect(self.__on_error)
        self.__myProcess.finished.connect(self.__on_exit)

        # -------------------------------- #
        #           Window setup           #
        # -------------------------------- #
        self.setGeometry(100, 100, 800, 200)
        self.setWindowTitle("QProcess test")

        self.__frm = QFrame(self)
        self.__frm.setStyleSheet("QWidget { background-color: #ffffff }")
        self.__lyt = QVBoxLayout()
        self.__lyt.setAlignment(Qt.AlignTop)
        self.__frm.setLayout(self.__lyt)
        self.setCentralWidget(self.__frm)

        self.__myBtn = QPushButton("START QPROCESS()")
        self.__myBtn.clicked.connect(self.__btn_clicked)
        self.__myBtn.setFixedHeight(70)
        self.__myBtn.setFixedWidth(200)
        self.__lyt.addWidget(self.__myBtn)
        self.show()

    def __add_openocd_to_env(self):
        self.__oldEnv = os.environ["PATH"]
        os.environ["PATH"] = "C:\\Users\\Kristof\\Dropbox (Personal)\\EMBEDOFFICE\\embedoffice\\resources\\programs\\openocd_0.10.0_dev00459\\bin;" + self.__oldEnv

    def __remove_openocd_from_env(self):
        os.environ["PATH"] = self.__oldEnv

    def __btn_clicked(self):
        self.__add_openocd_to_env()
        command = "openocd.exe" + '\r\n'
        self.__myProcess.start(command)
        self.__myProcess.waitForStarted(-1)
        self.__remove_openocd_from_env()

    def __on_output(self):
        data = bytes(self.__myProcess.readAll()).decode().replace('\r\n', '\n')
        print(data)

    def __on_error(self, error):
        print("")
        print("Process error: {0}".format(str(error)))
        print("")

    def __on_exit(self, exitCode, exitStatus):
        print("")
        print("ExitCode = {0}".format(str(exitCode)))
        print("ExitStatus = {0}".format(str(exitStatus)))
        print("")

if __name__ == '__main__':
    app = QApplication(sys.argv)
    QApplication.setStyle(QStyleFactory.create('Fusion'))
    myGUI = CustomMainWindow()
    sys.exit(app.exec_())

Basically I'm doing the following: just before ordering the QProcess() -instance to start a command, I add the executable path to the PATH environment variable that belongs to the whole Python session. Once the command has started, I can remove it again so it won't have an effect on other QProcess() -instances created in the future.

It works, but it will certainly require a lot of "bookkeeping" if I'm going to apply that approach in my software (many QProcess() -instances live in my software). If you find a better approach, please don't hesitate to share!

There is a solution using python subprocess.run() instead of QProcess.

In subprocess.run(), you can specify a set of environment variables (actually a dictionary) using the env parameter. The idea is to take a copy of your original environment, modify the PATH variable, and pass the modified environment to subprocess.run, as follows:

env = os.environ.copy()
env['PATH'] = "C:\\Users\\Kristof\\programs\\openocd_0.10.0\\bin" \
   + os.pathsep + env['PATH']
subprocess.run("openocd", env=env)

This still doesn't work: the remaining problem is that the environment (including the modified PATH variable) will be available in the subprocess but is not used to search for the openocd command. But that is easy to fix: subprocess.run also has a boolean shell parameter (default False) that tells it to run the command in a shell. Since the shell will run in the subprocess, it will use the modified PATH to search for openocd. So working code is:

env = os.environ.copy()
env['PATH'] = "C:\\Users\\Kristof\\programs\\openocd_0.10.0\\bin" \
   + os.pathsep + env['PATH']
subprocess.run("openocd", env=env, shell=True)

An alternative for shell=True is to use shutil.which (available in Python >= 3.3) to resolve the command. This will also work reliably when the command is given as a list of strings instead of a single string.

env = os.environ.copy()
env['PATH'] = "C:\\Users\\Kristof\\programs\\openocd_0.10.0\\bin" \
   + os.pathsep + env['PATH']
command = shutil.which("openocd", path = self.env.get('PATH', None))
subprocess.run([ command ], env=env)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM