Каков правильный способ заставить мое приложение PyQt выйти, когда оно убито с консоли (Ctrl-C)?



каков правильный способ заставить мое приложение PyQt выйти, когда оно убито с консоли (Ctrl-C)?



В настоящее время (я не сделал ничего особенного для обработки сигналов unix), мое приложение PyQt игнорирует SIGINT (Ctrl+C). Я хочу, чтобы он вел себя хорошо и ушел, когда его убьют. Как мне это сделать?

692   7  

7 ответов:

17.4. обработчики набора сигналов для асинхронных событий

хотя обработчики сигналов Python вызываются асинхронно, насколько это касается пользователя Python, они могут возникать только между "атомарными" инструкциями интерпретатора Python. Это означает, что сигналы, поступающие во время длительных вычислений, реализованных исключительно в C (например, совпадения регулярных выражений на больших объемах текста), могут задерживаться на произвольное количество время.

Это означает, что Python не может обрабатывать сигналы во время выполнения цикла событий Qt. Только когда интерпретатор Python запускается (когда QApplication завершает работу или когда функция Python вызывается из Qt), будет вызван обработчик сигнала.

решение состоит в том, чтобы использовать QTimer, чтобы время от времени запускать интерпретатор.

обратите внимание, что в приведенном ниже коде, если нет открытых окон, приложение завершит работу после окна сообщения независимо от пользователя выбор, потому что от QApplication.quitOnLastWindowClosed () = = True. Это поведение может быть изменено.

import signal
import sys

from PyQt4.QtCore import QTimer
from PyQt4.QtGui import QApplication, QMessageBox

# Your code here

def sigint_handler(*args):
    """Handler for the SIGINT signal."""
    sys.stderr.write('\r')
    if QMessageBox.question(None, '', "Are you sure you want to quit?",
                            QMessageBox.Yes | QMessageBox.No,
                            QMessageBox.No) == QMessageBox.Yes:
        QApplication.quit()

if __name__ == "__main__":
    signal.signal(signal.SIGINT, sigint_handler)
    app = QApplication(sys.argv)
    timer = QTimer()
    timer.start(500)  # You may change this if you wish.
    timer.timeout.connect(lambda: None)  # Let the interpreter run each 500 ms.
    # Your code here.
    sys.exit(app.exec_())

еще одно возможное решение,как указано LinearOrbit, составляет signal.signal(signal.SIGINT, signal.SIG_DFL), но это не позволяет пользовательские обработчики.

Если вы просто хотите, чтобы ctrl-c закрыл приложение-не будучи "приятным" / изящным об этом-тогда от http://www.mail-archive.com/[email protected]/msg13758.html, Вы можете использовать это:

import signal
signal.signal(signal.SIGINT, signal.SIG_DFL)

import sys
from PyQt4.QtCore import QCoreApplication
app = QCoreApplication(sys.argv)
app.exec_()

по-видимому, это работает на Linux, Windows и OSX - я только проверил это на Linux до сих пор (и это работает).

Я нашел способ сделать это. Идея состоит в том, чтобы заставить qt обрабатывать события достаточно часто и в Python callabe ловить сигнал SIGINT.

import signal, sys
from PyQt4.QtGui import QApplication, QWidget # also works with PySide

# You HAVE TO reimplement QApplication.event, otherwise it does not work.
# I believe that you need some python callable to catch the signal
# or KeyboardInterrupt exception.
class Application(QApplication):
    def event(self, e):
        return QApplication.event(self, e)

app = Application(sys.argv)

# Connect your cleanup function to signal.SIGINT
signal.signal(signal.SIGINT, lambda *a: app.quit())
# And start a timer to call Application.event repeatedly.
# You can change the timer parameter as you like.
app.startTimer(200)

w = QWidget()
w.show()
app.exec_()

18.8.1.1. Выполнение обработчиков сигналов Python

обработчик сигнала Python не выполняется внутри обработчика сигнала низкого уровня (C). Вместо этого низкоуровневый обработчик сигнала устанавливает флаг, который сообщает виртуальной машине выполнить соответствующий обработчик сигнала Python в более поздний момент (например, в следующей инструкции байт-кода). Это имеет последствия:
[...]
Долгосрочный расчет, реализованный исключительно в C (например регулярное сопоставление выражений на большом объеме текста) может выполняться непрерывно в течение произвольного периода времени, независимо от полученных сигналов. Обработчики сигналов Python будут вызваны после завершения вычисления.

цикл событий Qt реализован на языке C (++). Это означает, что пока он работает и не вызывается код Python (например. сигналом Qt, подключенным к слоту Python), сигналы отмечаются, но обработчики сигналов Python не являются называемый.

но, так как Python 2.6 и в Python 3 Вы можете заставить Qt запускать функцию Python, когда сигнал с обработчиком принимается с помощью signal.set_wakeup_fd().

это возможно, потому что, в отличие от документации, низкоуровневый обработчик сигналов не только устанавливает флаг для виртуальной машины, но и может записывать байт в файловый дескриптор, установленный set_wakeup_fd(). Python 2 записывает нулевой байт, Python 3 записывает сигнал число.

Итак, подкласс класса Qt, который принимает файловый дескриптор и предоставляет readReady() сигнал, как, например,QAbstractSocket, цикл событий будет выполнять функцию Python каждый раз, когда сигнал (с обработчиком) принимается вызывая обработчик сигнала для выполнения почти мгновенно без необходимости таймеров:

import sys, signal, socket
from PyQt4 import QtCore, QtNetwork

class SignalWakeupHandler(QtNetwork.QAbstractSocket):

    def __init__(self, parent=None):
        super().__init__(QtNetwork.QAbstractSocket.UdpSocket, parent)
        self.old_fd = None
        # Create a socket pair
        self.wsock, self.rsock = socket.socketpair(type=socket.SOCK_DGRAM)
        # Let Qt listen on the one end
        self.setSocketDescriptor(self.rsock.fileno())
        # And let Python write on the other end
        self.wsock.setblocking(False)
        self.old_fd = signal.set_wakeup_fd(self.wsock.fileno())
        # First Python code executed gets any exception from
        # the signal handler, so add a dummy handler first
        self.readyRead.connect(lambda : None)
        # Second handler does the real handling
        self.readyRead.connect(self._readSignal)

    def __del__(self):
        # Restore any old handler on deletion
        if self.old_fd is not None and signal and signal.set_wakeup_fd:
            signal.set_wakeup_fd(self.old_fd)

    def _readSignal(self):
        # Read the written byte.
        # Note: readyRead is blocked from occuring again until readData()
        # was called, so call it, even if you don't need the value.
        data = self.readData(1)
        # Emit a Qt signal for convenience
        self.signalReceived.emit(data[0])

    signalReceived = QtCore.pyqtSignal(int)

app = QApplication(sys.argv)
SignalWakeupHandler(app)

signal.signal(signal.SIGINT, lambda sig,_: app.quit())

sys.exit(app.exec_())

вы можете использовать стандартный механизм обработки сигналов python unix:

import signal 
import sys
def signal_handler(signal, frame):
        print 'You pressed Ctrl+C!'
        sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
print 'Press Ctrl+C'
while 1:
        continue

, где в signal_handler вы можете освободить все ресурсы (закрыть все сеансы БД и т. д.) и аккуратно закрыть приложение.

пример кода взят из здесь

Я думаю, что у меня есть более простое решение:

import signal
import PyQt4.QtGui

def handleIntSignal(signum, frame):
    '''Ask app to close if Ctrl+C is pressed.'''
    PyQt4.QtGui.qApp.closeAllWindows()

signal.signal(signal.SIGINT, handleIntSignal)

это просто говорит приложению, чтобы попытаться закрыть все окна, если ctrl+c нажата. Если есть несохраненный документ, ваше приложение должно открыть диалоговое окно Сохранить или отменить, как если бы оно было закрыто.

вам также может потребоваться подключить сигнал QApplication lastWindowClosed() к слоту quit (), чтобы заставить приложение фактически выйти, когда окна закрыты.

ответ от Артура Гаспара работал для меня, когда окно терминала было в фокусе, но не будет работать, когда графический интерфейс был в фокусе. Чтобы закрыть мой графический интерфейс (который наследуется от QWidget), мне нужно было определить следующую функцию в классе:

def keyPressEvent(self,event):
    if event.key() == 67 and (event.modifiers() & QtCore.Qt.ControlModifier):
        sigint_handler()

проверка, чтобы убедиться, что ключ события 67 удостоверяется, что 'c' был нажат. Затем проверка модификаторов событий определяет, был ли нажат ctrl, когда был выпущен "c".

Comments

    Ничего не найдено.