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