如何在其默认程序中启动文件,然后在脚本完成时关闭它?

2022-01-18 00:00:00 python windows shell subprocess popen

问题描述

总结

我有 wxPython GUI,它允许用户打开文件进行查看.目前我使用 os.startfile() 执行此操作.但是,我了解到这不是最好的方法,所以我正在寻求改进.startfile() 的主要缺点是文件一旦启动,我就无法控制它.这意味着用户可以将文件保持打开状态,因此其他用户将无法使用该文件.

我在寻找什么

在我的 GUI 中,可以有子窗口.我通过将 GUI 对象存储在一个列表中来跟踪所有这些,然后当父级关闭时,我只需遍历列表并关闭所有子级.我想对用户选择的任何文件做同样的事情.如何启动一个文件并保留一个 python 对象,以便我可以按命令关闭它?在此先感谢

我的解决方案梦想

  • 以这样一种方式启动文件,即有一个我可以在函数之间传递的 Python 对象
  • 在默认程序中启动文件并返回 PID 的某种方式
  • 一种通过文件名检索 PID 的方法

目前的进展

这是我计划使用的框架.重要的部分是 FileThread 类的 run()end() 函数,因为这是解决方案的所在.

导入 wx从 wx.lib.scrolledpanel 导入 ScrolledPanel导入线程导入操作系统类 GUI(wx.Frame):def __init__(self):wx.Frame.__init__(self, None, -1, '嘿,一个 GUI!', size=(300,300))self.panel = ScrolledPanel(父=self, id=-1)self.panel.SetupScrolling()self.Bind(wx.EVT_CLOSE, self.OnClose)self.openFiles = []self.openBtn = wx.Button(self.panel, -1, "打开文件")self.pollBtn = wx.Button(self.panel, -1, "投票")self.Bind(wx.EVT_BUTTON, self.OnOpen, self.openBtn)self.Bind(wx.EVT_BUTTON, self.OnPoll, self.pollBtn)vbox = wx.BoxSizer(wx.VERTICAL)vbox.Add((20,20), 1)vbox.Add(self.openBtn)vbox.Add((20,20), 1)vbox.Add(self.pollBtn)vbox.Add((20,20), 1)hbox = wx.BoxSizer(wx.HORIZONTAL)hbox.Add(vbox, flag=wx.TOP|wx.BOTTOM|wx.LEFT|wx.RIGHT|wx.EXPAND, 边框 = 10)self.panel.SetSizer(hbox)self.panel.Layout()def OnOpen(自我,事件):fileName = "AFileIWantToOpenWithTheFullPath.txt"self.openFiles.append(文件线程(文件名))def OnPoll(自我,事件):self.openFiles[0].Poll()def OnClose(自我,事件):对于 self.openFiles 中的文件:文件.end()self.openFiles.remove(文件)自我毁灭()类文件线程(线程.线程):def __init__(自我,文件):threading.Thread.__init__(self)self.file = 文件self.start()定义运行(自我):doc = subprocess.Popen(["start", "/MAX", "/WAIT", self.file], shell=True)返回文件def 民意调查(自我):打印轮询"打印 self.doc.poll()打印 self.doc.pid定义结束(自我):尝试:打印杀死文件{}".format(self.file)除了:print "文件已经被杀死"定义主():应用程序 = wx.PySimpleApp()gui = GUI()gui.Show()app.MainLoop()如果 __name__ == "__main__": main()

一些额外说明

  • 我不关心便携性,它只能在办公室周围的几台受控计算机上运行
  • 我认为这并不重要,但我正在通过批处理文件运行 pythonw 可执行文件

更新

我用 subprocess.Popen() 玩了一下,但遇到了同样的问题.我可以使用

制作 Popen 对象

doc = subprocess.Popen(["start", "Full\Path\to\File.txt"], shell=True)

但是当我poll() 对象时,它总是返回0.文档说 A None 值表示该进程尚未终止 所以 0 表示我的进程已终止.因此,尝试 kill() 它什么也不做.

我怀疑这是因为当 start 命令完成并启动文件时该过程完成.我想要一些即使在文件启动后也能继续运行的东西,这可以用 Popen() 完成吗?

解决方案

问题在于,在您的案例中,由 Popen 类处理的进程是 start shell 命令进程,它在运行与给定文件类型关联的应用程序后立即终止.解决方案是为 start 命令使用 /WAIT 标志,因此 start 进程等待其子进程终止.然后,使用例如 :

<块引用>

shell 参数(默认为 False)指定是否使用 shell 作为程序来执行.如果 shell 为 True,建议将 args 作为字符串而不是序列传递.

并按如下方式运行:

subprocess.Popen("start/WAIT" + self.file, shell=True)

Summary

I have wxPython GUI which allows the user to open files to view. Currently I do this with os.startfile(). However, I've come to learn that this is not the best method, so I'm looking to improve. The main drawback of startfile() is that I have no control over the file once it has been launched. This means that a user could leave a file open so it would be unavailable for another user.

What I'm Looking For

In my GUI, it is possible to have children windows. I keep track of all of these by storing the GUI objects in a list, then when the parent is closed, I just run through the list and close all the children. I would like to do the same with any file the user selects. How can I launch a file and retain a python object such that I can close it on command? Thanks in advance

My Dreams of a Solution

  • Launch the file in such a way that there is a Python object which I can pass between functions
  • Some way to launch a file in its default program and return the PID
  • A way to retrieve the PID with the file name

Progress So Far

Here's the frame I plan on using. The important bits are the run() and end() functions of the FileThread class as this is where the solution will go.

import wx
from wx.lib.scrolledpanel import ScrolledPanel 
import threading
import os

class GUI(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1, 'Hey, a GUI!', size=(300,300))
        self.panel = ScrolledPanel(parent=self, id=-1) 
        self.panel.SetupScrolling()  
        self.Bind(wx.EVT_CLOSE, self.OnClose)

        self.openFiles = []

        self.openBtn = wx.Button(self.panel, -1, "Open a File")
        self.pollBtn = wx.Button(self.panel, -1, "Poll")
        self.Bind(wx.EVT_BUTTON, self.OnOpen, self.openBtn)
        self.Bind(wx.EVT_BUTTON, self.OnPoll, self.pollBtn)

        vbox = wx.BoxSizer(wx.VERTICAL)

        vbox.Add((20,20), 1)
        vbox.Add(self.openBtn)
        vbox.Add((20,20), 1)
        vbox.Add(self.pollBtn)
        vbox.Add((20,20), 1)

        hbox = wx.BoxSizer(wx.HORIZONTAL)
        hbox.Add(vbox, flag=wx.TOP|wx.BOTTOM|wx.LEFT|wx.RIGHT|wx.EXPAND, border = 10)

        self.panel.SetSizer(hbox)
        self.panel.Layout()

    def OnOpen(self, event):
        fileName = "AFileIWantToOpenWithTheFullPath.txt"
        self.openFiles.append(FileThread(fileName))

    def OnPoll(self, event):
        self.openFiles[0].Poll()

    def OnClose(self, event):
        for file in self.openFiles:
            file.end()
            self.openFiles.remove(file)

        self.Destroy()

class FileThread(threading.Thread):
    def __init__(self, file):
        threading.Thread.__init__(self)
        self.file = file
        self.start()

    def run(self):
        doc = subprocess.Popen(["start", " /MAX", "/WAIT", self.file], shell=True)
        return doc

    def Poll(self):
        print "polling"
        print self.doc.poll()
        print self.doc.pid

    def end(self):
        try:
            print "killing file {}".format(self.file)
        except:
            print "file has already been killed"

def main():
    app = wx.PySimpleApp()
    gui = GUI()
    gui.Show()
    app.MainLoop()


if __name__ == "__main__": main()

Some Extra Notes

  • I'm not concerned with portability, this will only be run on a few controlled computers around the office
  • I don't think this matters, but I'm running the pythonw executable through a batch file

Update

I played around a bit with subprocess.Popen(), but ran into the same issue. I can make the Popen object using

doc = subprocess.Popen(["start", "Full\Path\to\File.txt"], shell=True)

but when I poll() the object, it always returns 0. The docs say that A None value indicates that the process hasn’t terminated yet so the 0 means that my process has terminated. Thus, attempting to kill() it does nothing.

I suspect this is because the process completes when the start command finishes and the file is launched. I want something that will keep going even after the file is launched, can this be done with Popen()?

解决方案

The problem lies within the fact, that the process being handled by the Popen class in your case is the start shell command process, which terminates just after it runs the application associated with given file type. The solution is to use the /WAIT flag for the start command, so the start process waits for its child to terminate. Then, using for example the psutil module you can achieve what you want with the following sequence:

>>> import psutil
>>> import subprocess
>>> doc = subprocess.Popen(["start", "/WAIT", "file.pdf"], shell=True)
>>> doc.poll()
>>> psutil.Process(doc.pid).get_children()[0].kill()
>>> doc.poll()
0
>>> 

After the third line Adobe Reader appears with the file opened. poll returns None as long as the window is open thanks to the /WAIT flag. After killing start's child Adobe Reader window disappears.

Other probably possible solution would be to find the application associated with given file type in the registry and run it without using start and shell=True.

I've tested this on 32 bit Python 2.7.5 on 64 bit Windows Vista, and 32 bit Python 2.7.2 on 64 bit Windows 7. Below is an example run on the latter. I've made some simple adjustments to your code - marked with a freehand red circles (!).

Also, possibly it is worth to consider this comment from the documentation:

The shell argument (which defaults to False) specifies whether to use the shell as the program to execute. If shell is True, it is recommended to pass args as a string rather than as a sequence.

and run the process as follows:

subprocess.Popen("start /WAIT " + self.file, shell=True)

相关文章