web-dev-qa-db-fra.com

Quelle est la bonne façon de faire que mon application PyQt se ferme lorsqu'elle est supprimée de la console (Ctrl-C)?

Quelle est la bonne façon de faire que mon application PyQt se ferme lorsqu'elle est supprimée de la console (Ctrl-C)?

Actuellement (je n'ai rien fait de spécial pour gérer les signaux Unix), mon application PyQt ignore SIGINT (Ctrl + C). Je veux qu'il se comporte bien et qu'il arrête quand il est tué. Comment devrais-je faire ça?

60
static_rtti

17.4. signal - Définit les gestionnaires pour les événements asynchrones

Bien que les gestionnaires de signaux Python soient appelés de manière asynchrone pour l'utilisateur Python, ils ne peuvent apparaître qu'entre les instructions «atomiques» de l'interpréteur Python. Cela signifie que les signaux arrivant lors de longs calculs mis en œuvre uniquement en C (tels que les correspondances d'expressions régulières sur de grands corps de texte) peuvent être retardés pendant une durée arbitraire.

Cela signifie que Python ne peut pas gérer les signaux pendant l'exécution de la boucle d'événements Qt. Ce n'est que lorsque l'interpréteur Python est exécuté (lorsque QApplication se ferme ou lorsqu'une fonction Python est appelée depuis Qt) que le gestionnaire de signal est appelé.

Une solution consiste à utiliser un QTimer pour permettre à l’interprète de fonctionner de temps en temps.

Notez que, dans le code ci-dessous, s'il n'y a pas de fenêtres ouvertes, l'application se fermera après le message, quel que soit le choix de l'utilisateur, car QApplication.quitOnLastWindowClosed () == True. Ce comportement peut être changé.

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

Une autre solution possible, comme l'a souligné LinearOrbit , est signal.signal(signal.SIGINT, signal.SIG_DFL), mais elle n'autorise pas les gestionnaires personnalisés.

41
Artur Gaspar

Si vous souhaitez simplement que ctrl-c ferme l'application - sans être "gentil"/gracieux à ce sujet - puis à partir de http: //www.mail-archive.com/[email protected]/msg13758 .html , vous pouvez utiliser ceci:

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

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

Apparemment, cela fonctionne sous Linux, Windows et OSX - je l’ai seulement testé jusqu’à présent (et ça marche).

36
LinearOrbit

18.8.1.1. Exécution de gestionnaires de signaux Python

Un gestionnaire de signaux Python n'est pas exécuté dans le gestionnaire de signaux de bas niveau (C). Au lieu de cela, le gestionnaire de signaux de bas niveau définit un indicateur qui indique à la machine virtuelle d'exécuter le gestionnaire de signaux Python correspondant ultérieurement (par exemple, lors de l'instruction de code intermédiaire suivante). Cela a des conséquences:
[...]
Un calcul de longue durée mis en œuvre uniquement en C (tel que la correspondance d'expression régulière sur un corps de texte volumineux) peut s'exécuter sans interruption pendant une durée arbitraire, quels que soient les signaux reçus. Les gestionnaires de signaux Python seront appelés à la fin du calcul.

La boucle d'événement Qt est implémentée en C (++). Cela signifie que pendant qu'il fonctionne et qu'aucun code Python n'est appelé (par exemple, par un signal Qt connecté à un emplacement Python), les signaux sont notés, mais les gestionnaires de signaux Python ne sont pas appelés.

Mais, depuis Python 2.6 et dans Python 3, vous pouvez obliger Qt à exécuter une fonction Python lorsqu'un signal avec un gestionnaire est reçu à l'aide de signal.set_wakeup_fd() .

Cela est possible car, contrairement à la documentation, le gestionnaire de signaux de bas niveau ne définit pas seulement un indicateur pour la machine virtuelle, mais il peut également écrire un octet dans le descripteur de fichier défini par set_wakeup_fd(). Python 2 écrit un octet NUL, Python 3 écrit le numéro du signal.

Donc, en sous-classant une classe Qt qui prend un descripteur de fichier et fournit un signal readReady(), comme par exemple. QAbstractSocket, la boucle d'événements exécute une fonction Python chaque fois qu'un signal (avec un gestionnaire) est reçu, ce qui entraîne l'exécution quasi instantanée du gestionnaire de signaux sans nécessiter de temporisateur:

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

J'ai trouvé un moyen de faire ça. L'idée est de forcer qt à traiter les événements assez souvent et en appelant en python pour attraper le signal 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_()
5
parkouss

Je pense avoir une solution plus simple:

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)

Cela indique simplement à l'application d'essayer de fermer toutes les fenêtres si vous appuyez sur ctrl + c. S'il existe un document non enregistré, votre application doit afficher une boîte de dialogue de sauvegarde ou d'annulation comme si elle avait été fermée.

Vous devrez peut-être également connecter le signal QApplication lastWindowClosed () à la fente quit () pour que l'application se ferme réellement lorsque les fenêtres sont fermées.

1
xioxox

Vous pouvez utiliser le mécanisme de traitement des signaux unix python standard:

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

où dans signal_handler vous pouvez libérer toutes les ressources (fermer toutes les sessions de la base de données, etc.) et fermer doucement votre application.

Exemple de code extrait de ici

1
karolx

La réponse d'Artur Gaspar a fonctionné pour moi lorsque la fenêtre du terminal était active, mais ne fonctionnerait pas lorsque l'interface graphique était active. Afin de fermer mon interface graphique (héritant de QWidget), je devais définir la fonction suivante dans la classe:

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

En vérifiant que la clé d’événement est 67, vous vous assurez que c a été enfoncé. Ensuite, la vérification des modificateurs d’événement détermine si ctrl a été activé lors de la publication de «c».

0
qwerty9967