Как запустить файл в своей программе по умолчанию, а затем закрыть его, когда заканчивается 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. Ниже приведен пример для последнего. Я сделал некоторые простые корректировки кода, отмеченные красными кругами (!).
![step1]()
![step2]()
![step3]()
![step4]()
Кроме того, возможно, стоит рассмотреть этот комментарий из документации:
Аргумент оболочки (по умолчанию False) указывает, следует ли использовать оболочку в качестве исполняемой программы. Если shell имеет значение True, рекомендуется передавать args как строку, а не как последовательность.
и выполните процесс следующим образом:
subprocess.Popen("start /WAIT " + self.file, shell=True)