GNU/Linux >> Tutoriels Linux >  >> Linux

Quelle est la bonne façon de faire fermer mon application PyQt lorsqu'elle est tuée depuis la console (Ctrl-C) ?

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 en ce qui concerne l'utilisateur Python, ils ne peuvent se produire qu'entre les instructions «atomiques» de l'interpréteur Python. Cela signifie que les signaux arrivant lors de longs calculs implémentés uniquement en C (comme 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 que la boucle d'événements Qt est en cours d'exécution. Ce n'est que lorsque l'interpréteur Python s'exécute (lorsque QApplication se ferme ou lorsqu'une fonction Python est appelée depuis Qt) que le gestionnaire de signal sera appelé.

Une solution consiste à utiliser un QTimer pour laisser l'interpréteur s'exécuter 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 la boîte de message quel que soit le choix de l'utilisateur car QApplication.quitOnLastWindowClosed() ==True. Ce comportement peut être modifié.

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

Une autre solution possible, comme indiqué par LinearOrbit, est signal.signal(signal.SIGINT, signal.SIG_DFL) , mais il n'autorise pas les gestionnaires personnalisés.


Si vous souhaitez simplement que ctrl-c ferme l'application - sans être "gentil"/gracieux à ce sujet - alors à 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 sur Linux, Windows et OSX - je n'ai testé cela que sur Linux jusqu'à présent (et cela fonctionne).


18.8.1.1. Exécution de gestionnaires de signaux Python

Un gestionnaire de signal Python n'est pas exécuté dans le gestionnaire de signal de bas niveau (C). Au lieu de cela, le gestionnaire de signal de bas niveau définit un indicateur qui indique à la machine virtuelle d'exécuter le gestionnaire de signal Python correspondant ultérieurement (par exemple à la prochaine instruction de bytecode). Cela a des conséquences :
[...]
Un calcul de longue durée implémenté uniquement en C (comme la correspondance d'expressions régulières sur un grand corps de texte) 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 une fois le calcul terminé.

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

Mais , depuis Python 2.6 et en Python 3, vous pouvez faire en sorte que Qt exécute une fonction Python lorsqu'un signal avec un gestionnaire est reçu en utilisant signal.set_wakeup_fd() .

Ceci est possible car, contrairement à la documentation, le gestionnaire de signal 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 de signal.

Donc, en sous-classant une classe Qt qui prend un descripteur de fichier et fournit un readReady() signal, comme par ex. QAbstractSocket , la boucle d'événements exécutera 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 signal sans avoir besoin de minuteries :

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

Linux
  1. Utiliser Reddit depuis la console en 2020

  2. Changer la disposition/le mappage du clavier sur la console (tty) et X d'une manière indépendante de X/console ?

  3. Lorsque vous tapez Ctrl-c dans un terminal, pourquoi le travail de premier plan n'est-il pas terminé jusqu'à ce qu'il soit terminé ? ?

  4. Python - La bonne façon de mettre à jour Pip dans Stretch Debian ?

  5. Quelle est la bonne façon d'ouvrir une plage de ports dans iptables

Quelle est la bonne façon d'installer jdk sur linux

Utilisez le Python par défaut plutôt que l'installation d'Anaconda lorsqu'il est appelé depuis le terminal

Quelle est la bonne façon d'empêcher les utilisateurs non root d'émettre des arrêts ou des redémarrages

Quel est le moyen le plus rapide de déplacer un million d'images d'un répertoire à un autre sous Linux ?

Qu'est-ce qui détermine exactement si un travail en arrière-plan est tué lorsque le shell est quitté, ou tué ?

Quel est le moyen le plus simple de visualiser les données de stdout sous forme de graphique ?