Найти - Пользователи
Полная версия: Асинхронный запуск нового процесса
Начало » Python для экспертов » Асинхронный запуск нового процесса
1 2 3 4
The gray Cardinal
В общем, Popen() под Linux срабатывает, только если записать так:

commandLine =

Причём второй элемент заключать в двойные кавычки нельзя - работать не будет.
Как быть, в целом?
В GUI хочется задавать строку (а не список), и чтобы всё это было кросс-платформенно. Это что, невозможно?
The gray Cardinal
poltergeist
Тогда ещё с тебя проверка правильности ввода коммандной строки, указание на ошибки пользователю и т.д. :) Красиво жить не запретишь…
Кстати, насчёт красивой жизни: например, в compizconfig-settings-manager, в разделе “General options” - “Commands” - “Commands” в GUI командные строки для шорткатов задаются именно строками, безо всяких списков аргументов и т.п. И сохранить в такой строке можно любую ерунду - никаких проверок.
shiza
насчет винды и кодировок не очень в курсе =)

насчет того чтоб все было строкой, пришел в голову не самый правильный вариант, но… рабочий =)

import subprocess

commandLine = u'gedit "/home/shiza/op.txt"'
commandLine = u'notepad.exe "c:\Python25\LICENSE.txt"'
subprocess.Popen(commandLine, shell = True)
фишка в том, чтоб запускать через шелл (запускается шелл и ему передается строка на запуск), который понимает все эти кавычки и прочее
gmorgunov
Привет
… У меня(в SUSE) вот это:
import subprocess
subprocess.Popen("ls"+" -l",shell=True)
Подвисает. Смотрю top - вроде все нормально, а завершить - только Ctrl+c.

А вот это:
import os
os.spawnl(os.P_WAIT, "/usr/bin/gedit","gedit",u"Ззыы")
Работает нормально :)
The gray Cardinal
shiza
пришел в голову не самый правильный вариант, но… рабочий
Спасибо, кажется, это то, что надо.
А почему “не самый правильный”? Медленно, т.к. shell вызывается?
shiza
почему-то он мне показался не очень красивым поначалу. Типа - вместо решения проблемы - ее обход.
The gray Cardinal
gmorgunov
Подвисает. Смотрю top - вроде все нормально, а завершить - только Ctrl+c.
Если я вызываю “ls -l” из своего GUI, таким же способом (shell=True), никаких “подвисаний” не замечено (openSUSE 11). Весь вывод, конечно, херится, но мне ведь именно этого и нужно.
gmorgunov
Еще раз посмотрел(openSUSE 10.3) и
subprocess.Popen("ls -l",shell=True)
и
subprocess.Popen("ls"+" -l",shell=True)
Выводит нормально(ls -l) и виснет… ( ты мне друг, но истина дороже… :) )
А вы попробуйте 2 раза подряд сделать( ./test_subprocess )
The gray Cardinal
gmorgunov
Выводит нормально(ls -l) и виснет
Нет, у меня точно не виснет, и если много раз подряд - тоже всё нормально. Может, тебе портит малину среда, из-под которой ты запускаешь (текстовый редактор, какая-то среда разработки).
The gray Cardinal
Блин.
gmorgunov абсолютно прав, я просто сразу не понял. Процесс оболочки при таком запуске будет находиться в памяти до тех пор, пока само приложение (запускаемое по моей чёртовой “командной строке”) не завершится. Это, конечно, крайне некрасиво :(. Возникла мысль кильнуть эту оболочку по pid'у, который есть в аттрибутах у класса Popen, но я не знаю, как это сделать кросс-платформенно. Да и вообще, некрасиво это всё как-то получилось.

В общем, я выкладываю здесь весь скрипт и очень прошу совета (или “патча” прямо к скрипту), как всё-таки поступить. Задача скрипта такова: это настраиваемая пользователем панелька для быстрого запуска GUI-приложений, написанная на pyQT4. Хотелось бы запускать командные строки пользователя без “висяков”, особенно в случае, если по ошибке запускается не GUI-приложение, причём кросс-платформенно. Может, стоит определить в коде операционку и сделать ветвление, запуская по-разному, но я не знаю, как именно это лучше сделать в такой ситуации.

Код рабочий, можно просто копировать в единственный файл с любым именем и запускать.
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys, glob, os, locale, subprocess, cPickle
from PyQt4 import QtGui, QtCore

class Dialog(QtGui.QDialog):
def __init__(self, parent=None, elem=None):
QtGui.QDialog.__init__(self, parent)
self.setMinimumWidth(400) # минимальная ширина окна диалога
self.setFixedHeight(100) # запрет изменения высоты

lay = QtGui.QGridLayout(self)
label1 = QtGui.QLabel(parent.tr.translate('QuickStarter', 'Name:'), self) # метка
label1.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
lay.addWidget(label1, 0, 0)
self.ln_edit1 = QtGui.QLineEdit('', self) # строковое поле ввода
self.ln_edit1.setMaxLength(100)
lay.addWidget(self.ln_edit1, 0, 1, 1, 2)
label2 = QtGui.QLabel(parent.tr.translate('QuickStarter', 'Command:'), self) # метка
label2.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
lay.addWidget(label2, 1, 0)
self.ln_edit2 = QtGui.QLineEdit('', self) # строковое поле ввода
self.ln_edit2.setMaxLength(255)
lay.addWidget(self.ln_edit2, 1, 1, 1, 2)
boxlay = QtGui.QHBoxLayout()
lay.addLayout(boxlay, 2, 2)
button1 = QtGui.QPushButton(parent.tr.translate('QuickStarter', 'Ok'), self) # кнопка
button1.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
self.connect(button1, QtCore.SIGNAL('clicked()'), lambda: self.done(1))
boxlay.addWidget(button1)
button2 = QtGui.QPushButton(parent.tr.translate('QuickStarter', 'Cancel'), self) # кнопка
button2.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
self.connect(button2, QtCore.SIGNAL('clicked()'), lambda: self.done(0))
boxlay.addWidget(button2)

if elem == None:
self.setWindowTitle(parent.tr.translate('QuickStarter', 'New element')) # заголовок окна
else:
self.setWindowTitle(parent.tr.translate('QuickStarter', 'Element edit')) # заголовок окна
self.ln_edit1.setText(elem.data(0, QtCore.Qt.DisplayRole).toString())
self.ln_edit2.setText(elem.data(1, QtCore.Qt.DisplayRole).toString())

class Translator(QtCore.QTranslator):
def __init__(self, parent=None, lang='QuickStarter_en_EN.qm'):
QtCore.QTranslator.__init__(self, parent)
self.load(lang)
def translate(self, context, sourceText):
res = QtCore.QTranslator.translate(self, context, sourceText)
if len(res) == 0:
res = QtCore.QString(sourceText)
return res

class MainWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QWidget.__init__(self)
home = os.path.realpath(os.path.dirname(sys.argv[0])) # каталог скрипта
self.setWindowIcon(QtGui.QIcon(home + '/qs64.bmp')) # иконка окна
menubar = self.menuBar() # строка меню
self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) # поверх всех окон

# объект для сохранения настроек приложения:
self.settings = QtCore.QSettings('script-coding.info', 'QuickStarter')

# восстановление языковой настройки:
self.lang = self.settings.value('lang', QtCore.QVariant(u'QuickStarter_en_EN.qm')).toString()
self.tr = Translator(None, self.lang)

# восстановление настройки геометрии окна:
self.setGeometry(self.settings.value('geometry', QtCore.QVariant(QtCore.QRect(300, 300, 350, 200))).toRect())

# заголовок окна:
self.setWindowTitle(self.tr.translate('QuickStarter', 'Quick starter'))

# меню "Language choice":
menuLang = menubar.addMenu(self.tr.translate('QuickStarter', 'Language choice'))

pointName = self.tr.translate('QuickStarter', 'By default')
point = QtGui.QAction(pointName, self)
self.connect(point, QtCore.SIGNAL('triggered()'), lambda: self.lang_menu('QuickStarter_en_EN.qm'))
menuLang.addAction(point)

for filename in glob.glob(home + '/QuickStarter_*_*.qm'):
pointName = os.path.basename(filename)
point = QtGui.QAction(pointName, self)
self.connect(point, QtCore.SIGNAL('triggered()'), lambda: self.lang_menu(pointName))
menuLang.addAction(point)

# выход по Escape:
menuLang.addSeparator()
exit = QtGui.QAction(self.tr.translate('QuickStarter', 'Exit'), self)
exit.setShortcut('Escape')
self.connect(exit, QtCore.SIGNAL('triggered()'), QtCore.SLOT('close()'))
menuLang.addAction(exit)

# дерево
self.tree = QtGui.QTreeWidget(self)
self.setCentralWidget(self.tree)
self.tree.headerItem().setHidden(True)
# обработчик двойного щелчка по дереву
self.connect(self.tree, QtCore.SIGNAL('itemDoubleClicked(QTreeWidgetItem *, int)'), self.doubleClick)

# восстановление содержимого дерева:
def restoreTreeItem(lst, parent = None):
item = QtGui.QTreeWidgetItem()
item.setData(0, QtCore.Qt.DisplayRole, QtCore.QVariant(lst[0]))
item.setData(1, QtCore.Qt.DisplayRole, QtCore.QVariant(lst[1]))
if parent:
parent.addChild(item)
else:
self.tree.addTopLevelItem(item)
if lst[3]:
self.tree.expandItem(item)
if lst[4]:
self.tree.setCurrentItem(item)
for elem in lst[2]:
restoreTreeItem(elem, item)

dump = str(self.settings.value('tree', QtCore.QVariant('(lp1\n.')).toString())
self.treeList = cPickle.loads(dump)
for elem in self.treeList:
restoreTreeItem(elem)

# меню "Edit"
menuEdit = menubar.addMenu(self.tr.translate('QuickStarter', 'Edit'))

nm = self.tr.translate('QuickStarter', 'Add an element of the root')
self.addRoot = QtGui.QAction(nm, self)
self.connect(self.addRoot, QtCore.SIGNAL('triggered()'), self.addRootEvent)
menuEdit.addAction(self.addRoot)

nm = self.tr.translate('QuickStarter', 'Add an element')
self.add = QtGui.QAction(nm, self)
self.connect(self.add, QtCore.SIGNAL('triggered()'), self.addEvent)
menuEdit.addAction(self.add)

nm = self.tr.translate('QuickStarter', 'Delete the element')
self.delete = QtGui.QAction(nm, self)
self.delete.setShortcut('Delete')
self.connect(self.delete, QtCore.SIGNAL('triggered()'), self.deleteEvent)
menuEdit.addAction(self.delete)

nm = self.tr.translate('QuickStarter', 'Edit the element')
self.edit = QtGui.QAction(nm, self)
self.edit.setShortcut('Return')
self.connect(self.edit, QtCore.SIGNAL('triggered()'), self.editEvent)
menuEdit.addAction(self.edit)

# меню "Running"
menuEdit = menubar.addMenu(self.tr.translate('QuickStarter', 'Running'))

nm = self.tr.translate('QuickStarter', 'Run')
self.run = QtGui.QAction(nm, self)
self.run.setShortcut('Space')
self.connect(self.run, QtCore.SIGNAL('triggered()'), self.runEvent)
menuEdit.addAction(self.run)

nm = self.tr.translate('QuickStarter', 'Run and do not exit')
self.runNotExit = QtGui.QAction(nm, self)
self.runNotExit.setShortcut('Ctrl+Space')
self.connect(self.runNotExit, QtCore.SIGNAL('triggered()'), lambda: self.runEvent(False))
menuEdit.addAction(self.runNotExit)

def runEvent(self, exit = True):
# Обработчик пункта меню "Run".
currItem = self.tree.currentItem()
if currItem == None:
caption = self.tr.translate('QuickStarter', 'Quick starter')
text = self.tr.translate('QuickStarter', 'Do not chosen element!')
QtGui.QMessageBox.information(self, caption, text, QtGui.QMessageBox.Ok)
return
commandLine = unicode(currItem.data(1, QtCore.Qt.DisplayRole).toString())
commandLine = commandLine.encode(locale.getdefaultlocale()[1])
commandLine = os.path.expanduser(commandLine) # обработка "~"
try:
popen = subprocess.Popen(commandLine, shell = True) # запуск
if exit:
self.close()
except:
caption = self.tr.translate('QuickStarter', 'Quick starter')
text = self.tr.translate('QuickStarter', 'Failed to run the application.') + '\n' + \
self.tr.translate('QuickStarter', 'Please check the command line and / or your permission to the files.')
QtGui.QMessageBox.information(self, caption, text, QtGui.QMessageBox.Ok)

def doubleClick(self, item, column):
# Обработчик двойного щелчка по дереву.
self.editEvent()

def contextMenuEvent(self, event):
# Обработчик контекстного меню.
menu = QtGui.QMenu(self)
menu.addAction(self.addRoot)
menu.addAction(self.add)
menu.addAction(self.delete)
menu.addAction(self.edit)
menu.addSeparator()
menu.addAction(self.run)
menu.addAction(self.runNotExit)
menu.exec_(event.globalPos())

def addRootEvent(self):
# Обработчик пункта меню "Add an element of the root".
dlg = Dialog(self)
if dlg.exec_(): # нажата кнопка "Ок"
item = QtGui.QTreeWidgetItem()
nm = dlg.ln_edit1.text()
if len(nm) == 0: nm = self.tr.translate('QuickStarter', 'Untitled')
item.setData(0, QtCore.Qt.DisplayRole, QtCore.QVariant(nm))
item.setData(1, QtCore.Qt.DisplayRole, QtCore.QVariant(dlg.ln_edit2.text()))
self.tree.addTopLevelItem(item)
self.tree.sortItems(0, QtCore.Qt.AscendingOrder)
dlg.destroy()

def addEvent(self):
# Обработчик пункта меню "Add an element".
parentItem = self.tree.currentItem()
if parentItem == None:
caption = self.tr.translate('QuickStarter', 'Quick starter')
text = self.tr.translate('QuickStarter', 'Do not chosen parent element!')
QtGui.QMessageBox.information(self, caption, text, QtGui.QMessageBox.Ok)
return
dlg = Dialog(self)
if dlg.exec_(): # нажата кнопка "Ок"
item = QtGui.QTreeWidgetItem()
nm = dlg.ln_edit1.text()
if len(nm) == 0: nm = self.tr.translate('QuickStarter', 'Untitled')
item.setData(0, QtCore.Qt.DisplayRole, QtCore.QVariant(nm))
item.setData(1, QtCore.Qt.DisplayRole, QtCore.QVariant(dlg.ln_edit2.text()))
parentItem.addChild(item)
self.tree.sortItems(0, QtCore.Qt.AscendingOrder)
dlg.destroy()

def deleteEvent(self):
# Обработчик пункта меню "Delete the element".
currItem = self.tree.currentItem()
if currItem == None:
caption = self.tr.translate('QuickStarter', 'Quick starter')
text = self.tr.translate('QuickStarter', 'Do not chosen element!')
QtGui.QMessageBox.information(self, caption, text, QtGui.QMessageBox.Ok)
return
caption = self.tr.translate('QuickStarter', 'Quick starter')
text1 = self.tr.translate('QuickStarter', 'Deleting element')
text2 = self.tr.translate('QuickStarter', 'Continue?')
nm = ' "' + currItem.data(0, QtCore.Qt.DisplayRole).toString() + '".\n '
text = text1 + nm + text2
reply = QtGui.QMessageBox.question(self, caption, text, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.No:
return
parent = currItem.parent()
if parent != None:
parent.takeChild(parent.indexOfChild(currItem))
else:
self.tree.takeTopLevelItem(self.tree.indexOfTopLevelItem(currItem))

def editEvent(self):
# Обработчик пункта меню "Edit the element".
currItem = self.tree.currentItem()
if currItem == None:
caption = self.tr.translate('QuickStarter', 'Quick starter')
text = self.tr.translate('QuickStarter', 'Do not chosen element!')
QtGui.QMessageBox.information(self, caption, text, QtGui.QMessageBox.Ok)
return
dlg = Dialog(self, currItem)
if dlg.exec_(): # нажата кнопка "Ок"
nm = dlg.ln_edit1.text()
if len(nm) == 0: nm = self.tr.translate('QuickStarter', 'Untitled')
currItem.setData(0, QtCore.Qt.DisplayRole, QtCore.QVariant(nm))
currItem.setData(1, QtCore.Qt.DisplayRole, QtCore.QVariant(dlg.ln_edit2.text()))
self.tree.sortItems(0, QtCore.Qt.AscendingOrder)
dlg.destroy()

def lang_menu(self, pointName):
# Обработчик любого пункта колонки меню "Language choice".
self.lang = pointName
caption = self.tr.translate('QuickStarter', 'Quick starter')
text = self.tr.translate('QuickStarter', 'In order for language setting to come into force, restart the application.')
QtGui.QMessageBox.information(self, caption, text, QtGui.QMessageBox.Ok)

def closeEvent(self, event):
# сохранение языковой настройки:
self.settings.setValue('lang', QtCore.QVariant(self.lang))
# сохранение настройки геометрии окна:
self.settings.setValue('geometry', QtCore.QVariant(self.geometry()))
# сохранение содержимого дерева:
self.treeList = []
for i in xrange(self.tree.topLevelItemCount()):
topLevelItem = self.tree.topLevelItem(i)
lst = []
self.treeList.append(lst)
self.serializeTreeItem(topLevelItem, lst)
dump = cPickle.dumps(self.treeList)
self.settings.setValue('tree', QtCore.QVariant(dump))

def serializeTreeItem(self, item, lst):
nm = item.data(0, QtCore.Qt.DisplayRole).toString()
cm = item.data(1, QtCore.Qt.DisplayRole).toString()
lst.append(nm) # первый элемент - наименование
lst.append(cm) # второй элемент - командная строка
lstChild = []
lst.append(lstChild) # третий элемент - список дочерних
lst.append(item.isExpanded()) # четвёртый элемент - признак "раскрытости"
if self.tree.currentItem() == item:
lst.append(True) # пятый элемент - признак текущего элемента
else:
lst.append(False) # пятый элемент - признак текущего элемента
for i in xrange(item.childCount()):
elem = []
lstChild.append(elem)
self.serializeTreeItem(item.child(i), elem)

if __name__=="__main__":
app = QtGui.QApplication(sys.argv)
app.setStyle("Plastique")
main = MainWindow()
main.show()
sys.exit(app.exec_())
В архиве - иконка и языковой файл, должны лежать рядом со скриптом.
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB