it-swarm.com.de

Wie kann ich meine PyQt-Anwendung beim Beenden von der Konsole (Ctrl-C) beenden?

Wie kann ich meine PyQt-Anwendung beim Beenden von der Konsole (Ctrl-C) beenden?

Derzeit (ich habe nichts Besonderes mit Unix-Signalen gemacht), ignoriert meine PyQt-Anwendung SIGINT (Strg + C). Ich möchte, dass es sich nett verhält und aufgibt, wenn es getötet wird. Wie soll ich das machen?

60
static_rtti

17,4. signal - Handler für asynchrone Ereignisse setzen

Obwohl Python-Signal-Handler vom Python-Benutzer aus asynchron aufgerufen werden, können sie nur zwischen den "atomaren" Anweisungen des Python-Interpreters auftreten. Dies bedeutet, dass Signale, die während langer Berechnungen eintreffen, die rein in C implementiert werden (z. B. Übereinstimmungen mit regulären Ausdrücken in großen Textkörpern), für eine beliebige Zeitdauer verzögert werden können.

Das bedeutet, dass Python keine Signale verarbeiten kann, während die Qt-Ereignisschleife ausgeführt wird. Nur wenn der Python-Interpreter ausgeführt wird (wenn die QApplication beendet wird oder wenn eine Python-Funktion von Qt aufgerufen wird), wird der Signal-Handler aufgerufen.

Eine Lösung besteht darin, einen QTimer zu verwenden, um den Interpreter von Zeit zu Zeit laufen zu lassen.

Beachten Sie, dass im folgenden Code die Anwendung nach dem Meldungsfeld beendet wird, wenn kein Fenster geöffnet ist, unabhängig von der Benutzerauswahl, da QApplication.quitOnLastWindowClosed () == True ist. Dieses Verhalten kann geändert werden.

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 __== "__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_())

Eine andere mögliche Lösung, wie LinearOrbit zeigt, ist signal.signal(signal.SIGINT, signal.SIG_DFL), erlaubt aber keine benutzerdefinierten Handler.

41
Artur Gaspar

Wenn Sie einfach möchten, dass Strg-C die Anwendung schließt, ohne "nett"/anmutig zu sein, dann von http: //www.mailarchive.com/[email protected]/msg13758 .html , können Sie Folgendes verwenden:

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

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

Anscheinend funktioniert das unter Linux, Windows und OSX - ich habe das bisher nur unter Linux getestet (und es funktioniert).

36
LinearOrbit

18.8.1.1. Ausführung von Python-Signalhandlern

Ein Python-Signalhandler wird nicht im Low-Level-Signalhandler (C) ausgeführt. Stattdessen setzt der Low-Level-Signal-Handler ein Flag, das die virtuelle Maschine anweist, den entsprechenden Python-Signal-Handler zu einem späteren Zeitpunkt auszuführen (zum Beispiel beim nächsten Bytecode-Befehl). Das hat Konsequenzen:
[...]
Eine langwierige, rein in C implementierte Berechnung (wie z. B. das Übereinstimmen eines regulären Ausdrucks in einem großen Textkörper) kann unabhängig von empfangenen Signalen für eine beliebige Zeitdauer ununterbrochen ausgeführt werden. Die Python-Signalhandler werden aufgerufen, wenn die Berechnung abgeschlossen ist.

Die Qt-Ereignisschleife ist in C (++) implementiert. Das bedeutet, dass der Python-Code zwar ausgeführt wird und kein Python-Code aufgerufen wird (z. B. durch ein mit einem Python-Slot verbundenes Qt-Signal), die Signale jedoch notiert werden, die Python-Signalhandler jedoch nicht aufgerufen werden.

Aber, seit Python 2.6 und in Python 3 können Sie Qt dazu veranlassen, eine Python-Funktion auszuführen, wenn ein Signal mit einem Handler mit signal.set_wakeup_fd() empfangen wird.

Dies ist möglich, da der Low-Level-Signalhandler im Gegensatz zur Dokumentation nicht nur ein Flag für die virtuelle Maschine setzt, sondern auch ein Byte in den von set_wakeup_fd() festgelegten Dateideskriptor schreibt. Python 2 schreibt ein NUL-Byte, Python 3 schreibt die Signalnummer.

Durch Unterklassen einer Qt-Klasse, die einen Dateideskriptor übernimmt und ein readReady()-Signal liefert, wie z. QAbstractSocket führt die Ereignisschleife jedes Mal eine Python-Funktion aus, wenn ein Signal (mit einem Handler) empfangen wird, wodurch der Signalhandler nahezu augenblicklich ohne Zeitgeber ausgeführt wird:

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_())
6
cg909

Ich habe einen Weg gefunden, dies zu tun. Die Idee ist, qt zu zwingen, Ereignisse oft genug zu verarbeiten, und in einer Python-Callabe das SIGINT-Signal einzufangen.

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_()
5
parkouss

Ich denke, ich habe eine einfachere Lösung:

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)

Dadurch wird der Anwendung lediglich mitgeteilt, dass versucht werden soll, alle Fenster zu schließen, wenn Strg + C gedrückt wird. Wenn ein nicht gespeichertes Dokument vorhanden ist, sollte Ihre App ein Dialogfeld zum Speichern oder Abbrechen anzeigen, als wäre sie beendet worden.

Möglicherweise müssen Sie auch das QApplication-Signal lastWindowClosed () an den Slot quit () anschließen, damit die Anwendung beim Schließen der Fenster tatsächlich beendet wird.

1
xioxox

Sie können den standardmäßigen Python-Unix-Signalbehandlungsmechanismus verwenden:

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

in signal_handler können Sie alle Ressourcen freigeben (alle Db-Sitzungen schließen usw.) und Ihre Anwendung vorsichtig schließen.

Code-Beispiel aus hier

1
karolx

Die Antwort von Artur Gaspar funktionierte für mich, als das Terminalfenster im Fokus stand, aber es funktionierte nicht, wenn die GUI im Fokus war. Um meine GUI zum Schließen zu bringen (die von QWidget erbt), musste ich die folgende Funktion in der Klasse definieren:

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

Wenn Sie sicherstellen, dass die Ereignistaste auf 67 steht, stellen Sie sicher, dass "c" gedrückt wurde. Durch Überprüfen der Ereignismodifizierer wird festgestellt, ob beim Loslassen von 'c' die Strg-Taste gedrückt wurde.

0
qwerty9967