Как запустить файл в своей программе по умолчанию, а затем закрыть его, когда заканчивается script?

Резюме

У меня есть wxPython GUI, который позволяет пользователю открывать файлы для просмотра. В настоящее время я делаю это с помощью os.startfile(). Тем не менее, я пришел, чтобы узнать, что это не лучший метод, поэтому я хочу улучшить. Главный недостаток startfile() заключается в том, что я не могу контролировать файл после его запуска. Это означает, что пользователь может оставить файл открытым, чтобы он не был доступен другому пользователю.

Что я ищу

В моем графическом интерфейсе возможно иметь дочерние окна. Я отслеживаю все это, сохраняя объекты 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()

Некоторые дополнительные заметки

  • Меня не интересует переносимость, это будет работать только на нескольких управляемых компьютерах в офисе.
  • Я не думаю, что это имеет значение, но я запускаю исполняемый файл 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()?

Ответ 1

Проблема заключается в том, что процесс, обрабатываемый классом Popen в вашем случае, - это командный процесс оболочки start, который заканчивается сразу после запуска приложения, связанного с данным типом файла. Решение состоит в использовании флага /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
>>> 

После третьей строки появится Adobe Reader с открытым файлом. poll возвращает None, пока окно открыто с помощью флага /WAIT. После убийства start дочернее окно Adobe Reader исчезает.

Другим возможным решением было бы найти приложение, связанное с данным типом файла в реестре, и запустить его без использования start и shell=True.

Я тестировал это на 32-разрядном Python 2.7.5 на 64-битной Windows Vista и 32-битном Python 2.7.2 на 64-битной Windows 7. Ниже приведен пример для последнего. Я сделал некоторые простые корректировки кода, отмеченные красными кругами (!).

step1step2step3step4

Кроме того, возможно, стоит рассмотреть этот комментарий из документации:

Аргумент оболочки (по умолчанию False) указывает, следует ли использовать оболочку в качестве исполняемой программы. Если shell имеет значение True, рекомендуется передавать args как строку, а не как последовательность.

и выполните процесс следующим образом:

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