简体   繁体   English

如何为QProcess PATH环境变量添加路径? (Python 3.7上的PyQt5)

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

1. The problem explained 1.问题解释

I instantiate a QProcess() -object just before the application shows its main window. 我在应用程序显示其主窗口之前实例化了QProcess() The QProcess() -instance is stored in the self.__myProcess variable, and stays alive as long as you can see the main window. QProcess() self.__myProcess存储在self.__myProcess变量中,只要您可以看到主窗口, self.__myProcess保持活动状态。

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. 最后两行很清楚:将openocd.exe命令传递给self.__myProcess并执行。 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. 关键是:如果可执行文件在我的Windows PATH环境变量中,则会找到并执行它。

Imagine the executable is NOT in the PATH environment variable. 假设可执行文件PATH环境变量中。 Then the function self.__add_openocd_to_env() should fix that issue: 然后,函数self.__add_openocd_to_env()应该解决该问题:

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. 如果您在PyQt5上安装了Python 3,则只需将代码复制粘贴到.py模块中并运行它。 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. 当然,您应该将路径“ C:\\ Users \\ Kristof ..”更改为计算机上有效的名称。 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 2.我的问题

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() . 我知道我可以在实例化QProcess()之前将“ C:\\ Users \\ Kristof \\ programs \\ openocd_0.10.0 \\ bin”添加到Windows PATH环境变量中。 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. 我想知道如何将其添加到该特定QProcess() instance的PATH环境变量中。 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. 如果可能,它不应影响软件中的任何其他QProcess()实例,也不应影响我以后创建的任何将来的QProcess()实例。

3. System settings 3.系统设定

I use the PyQt5 framework in Python 3.7 on Windows 10. 我在Windows 10的Python 3.7中使用PyQt5框架。


NOTE: 注意:
I've just tried to improve the QProcess() setup in the following way: 我刚刚尝试通过以下方式改进QProcess()设置:

        # -------------------------------- #
        #          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: 基于@JonBrave先生的评论,我编写了以下解决方法:

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. 基本上,我正在执行以下操作:在命令QProcess() instance开始命令之前,我将可执行路径添加到属于整个Python会话的PATH环境变量中。 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. 命令启动后,我可以再次将其删除,以免对以后创建的其他QProcess()实例产生影响。

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). 它可以工作,但是如果我要在软件中应用这种方法(许多QProcess() instances都存在于我的软件中),肯定会需要很多“簿记”操作。 If you find a better approach, please don't hesitate to share! 如果您找到更好的方法,请随时分享!

There is a solution using python subprocess.run() instead of QProcess. 有一个使用python subprocess.run()而不是QProcess的解决方案。

In subprocess.run(), you can specify a set of environment variables (actually a dictionary) using the env parameter. 在subprocess.run()中,可以使用env参数指定一组环境变量(实际上是字典)。 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: 这个想法是复制原始环境,修改PATH变量,然后将修改后的环境传递给subprocess.run,如下所示:

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. 这仍然不起作用:剩下的问题是该环境(包括修改后的PATH变量)将在子进程中可用,但不能用于搜索openocd命令。 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. 但这很容易解决:subprocess.run也有一个布尔shell参数(默认为False),告诉它在shell中运行命令。 Since the shell will run in the subprocess, it will use the modified PATH to search for openocd. 由于外壳程序将在子进程中运行,因此它将使用修改后的PATH搜索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. shell = True的替代方法是使用shutil.which(在Python> = 3.3中可用)来解析命令。 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)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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