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

wnn*_*maw 13 python windows shell subprocess popen

摘要

我有wxPython GUI,允许用户打开文件进行查看.目前我这样做os.startfile().但是,我已经知道这不是最好的方法,所以我希望改进.主要的缺点startfile()是,一旦启动文件,我就无法控制文件.这意味着用户可以将文件保持打开状态,以便其他用户无法使用.

我正在寻找什么

在我的GUI中,可以有子窗口.我通过将GUI对象存储在列表中来跟踪所有这些,然后当父对象关闭时,我只是遍历列表并关闭所有子对象.我想对用户选择的任何文件执行相同操作.如何启动文件并保留python对象,以便我可以在命令中关闭它?提前致谢

我的解决方案的梦想

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

进展到目前为止

这是我计划使用的框架.最重要的位是run()end()该功能FileThread类,因为这是在解决方案会去.

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()
Run Code Online (Sandbox Code Playgroud)

一些额外的笔记

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

更新

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

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

但是当我poll()成为对象时,它总会返回0.文档说,A None value indicates that the process hasn’t terminated yet0意味着我的流程终止了.因此,尝试kill()它什么都不做.

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

Bar*_*zKP 7

问题在于,Popen在您的情况下由类处理的进程是startshell命令进程,它在运行与给定文件类型关联的应用程序之后终止.解决方案是使用命令的/WAIT标志start,因此start进程等待其子进程终止.然后,使用例如psutil模块,您可以使用以下顺序实现所需的模块:

>>> 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
>>> 
Run Code Online (Sandbox Code Playgroud)

在第三行Adobe Reader出现后打开文件.由于旗帜,只要窗口打开就会poll返回.杀死孩子后,Adobe Reader窗口消失了.None/WAITstart

其他可能的解决方案是在注册表中找到与给定文件类型相关联的应用程序,并在不使用start和运行它的情况下运行它shell=True.

我已经在64位Windows Vista上的32位Python 2.7.5和64位Windows 7上的32位Python 2.7.2上进行了测试.下面是对后者运行的示例.我对你的代码进行了一些简单的调整 - 用手绘的红色圆圈(!)标记.

步骤1 第2步 第三步: 第4步

此外,可能值得从文档中考虑此评论:

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

并按如下方式运行该过程:

subprocess.Popen("start /WAIT " + self.file, shell=True)
Run Code Online (Sandbox Code Playgroud)