【问题标题】:How do I launch a file in its default program, and then close it when the script finishes?如何在其默认程序中启动文件,然后在脚本完成时关闭它?
【发布时间】:2014-01-13 16:29:05
【问题描述】:

总结

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

我在寻找什么

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

我的解决方案梦想

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

目前的进展

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

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()

一些额外说明

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

更新

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

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

但是当我poll() 对象时,它总是返回0。文档说A None value indicates that the process hasn’t terminated yet 所以0 意味着我的进程已经终止。因此,尝试kill() 它什么也不做。

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

【问题讨论】:

  • 我认为每个文件的默认程序的概念并不容易移植。如果你在 Freedesktop.org 系统上,xdg-open 是票,在 OSX 上它只是简单的open,而在 Windows 上,我相信你通常会使用start(并让它以新的和有趣的方式失败出于非显而易见的原因,如果以前的表现是任何指标)。
  • @tripleee 我不太关心可移植性,因为这是用于受控办公室计算机上的脚本。
  • @wnnmaw 这个startfile 功能与触发子进程完全不同。它创建了一个独立进程,即使你有 PID,它也不会对你有任何好处,因为没有 root 权限你将无法杀死它。
  • @freakish 我不想使用startfile的另一个原因@

标签: python windows shell subprocess popen


【解决方案1】:

问题在于,在您的情况下,由Popen 类处理的进程是start shell 命令进程,它在运行与给定文件类型关联的应用程序后立即终止。解决方案是为start 命令使用/WAIT 标志,因此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
>>> 

在打开文件的第三行之后,Adobe Reader 出现。由于/WAIT 标志,只要窗口打开,poll 就会返回None。杀死start 的子Adobe Reader 窗口后消失。

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

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

另外,可能值得考虑来自documentation 的这条评论:

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

并按如下方式运行:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多