Как правильно сделать так, чтобы мое приложение PyQt закрывалось при уничтожении из консоли (Ctrl-C)? - PullRequest
63 голосов
/ 09 февраля 2011

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

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

Ответы [ 7 ]

42 голосов
/ 09 февраля 2011

17,4. signal - установить обработчики для асинхронных событий

Хотя обработчики сигналов 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), но оно не допускает пользовательских обработчиков.

39 голосов
/ 20 мая 2011

Если вы просто хотите, чтобы ctrl-c закрыл приложение - не будь «милым» / изящным в этом - тогда с http://www.mail-archive.com/pyqt@riverbankcomputing.com/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 (и это работает).

7 голосов
/ 14 мая 2016

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 записывает байт NUL, 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_())
5 голосов
/ 29 июля 2012

Я нашел способ сделать это. Идея состоит в том, чтобы заставить qt достаточно часто обрабатывать события и вызывать в Python функцию вызова сигнала 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_()
1 голос
/ 15 февраля 2011

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

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 (), чтобы получитьприложение для фактического выхода при закрытии окон.

1 голос
/ 09 февраля 2011

Вы можете использовать стандартный механизм обработки сигналов 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 вы можете освободить все ресурсы (закрыть все сеансы БД и т. Д.) И аккуратно закрыть приложение.1006 * Пример кода взят из здесь

0 голосов
/ 17 июня 2014

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

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

Проверка, чтобы убедиться, что клавиша события - 67, удостоверяется, что была нажата 'c'. Затем проверка модификаторов событий определяет, была ли нажата Ctrl при отпускании «c».

...