GNU/Linux >> Linux Esercitazione >  >> Linux

Qual è il modo corretto per chiudere la mia applicazione PyQt quando viene uccisa dalla console (Ctrl-C)?

17.4. signal — Imposta gestori per eventi asincroni

Sebbene i gestori di segnali Python siano chiamati in modo asincrono per quanto riguarda l'utente Python, possono verificarsi solo tra le istruzioni "atomiche" dell'interprete Python. Ciò significa che i segnali che arrivano durante lunghi calcoli implementati esclusivamente in C (come le corrispondenze di espressioni regolari su grandi corpi di testo) possono essere ritardati per un periodo di tempo arbitrario.

Ciò significa che Python non può gestire i segnali mentre il ciclo di eventi Qt è in esecuzione. Solo quando viene eseguito l'interprete Python (quando QApplication si chiude o quando una funzione Python viene chiamata da Qt) verrà chiamato il gestore di segnale.

Una soluzione è utilizzare un QTimer per far eseguire l'interprete di tanto in tanto.

Si noti che, nel codice seguente, se non ci sono finestre aperte, l'applicazione si chiuderà dopo la finestra di messaggio indipendentemente dalla scelta dell'utente perché QApplication.quitOnLastWindowClosed() ==True. Questo comportamento può essere modificato.

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

Un'altra possibile soluzione, come indicato da LinearOrbit, è signal.signal(signal.SIGINT, signal.SIG_DFL) , ma non consente gestori personalizzati.


Se desideri semplicemente che ctrl-c chiuda l'applicazione - senza essere "gentile"/grazioso al riguardo - allora da http://www.mail-archive.com/[email protected]/msg13758.html, puoi usare questo:

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

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

Apparentemente funziona su Linux, Windows e OSX:finora l'ho testato solo su Linux (e funziona).


18.8.1.1. Esecuzione di gestori di segnali Python

Un gestore di segnale Python non viene eseguito all'interno del gestore di segnale di basso livello (C). Invece, il gestore di segnale di basso livello imposta un flag che dice alla macchina virtuale di eseguire il corrispondente gestore di segnale Python in un momento successivo (ad esempio alla successiva istruzione bytecode). Questo ha delle conseguenze:
[...]
Un calcolo di lunga durata implementato esclusivamente in C (come la corrispondenza di espressioni regolari su un corpo di testo di grandi dimensioni) può essere eseguito ininterrottamente per un periodo di tempo arbitrario, indipendentemente da eventuali segnali ricevuti. I gestori di segnale Python verranno chiamati al termine del calcolo.

Il ciclo di eventi Qt è implementato in C(++). Ciò significa che mentre viene eseguito e non viene chiamato alcun codice Python (ad es. da un segnale Qt connesso a uno slot Python), i segnali vengono annotati, ma i gestori di segnali Python non vengono chiamati.

Ma , poiché Python 2.6 e in Python 3 puoi fare in modo che Qt esegua una funzione Python quando viene ricevuto un segnale con un gestore usando signal.set_wakeup_fd() .

Ciò è possibile perché, contrariamente alla documentazione, il gestore del segnale di basso livello non solo imposta un flag per la macchina virtuale, ma può anche scrivere un byte nel descrittore di file impostato da set_wakeup_fd() . Python 2 scrive un byte NUL, Python 3 scrive il numero del segnale.

Quindi, creando una sottoclasse di una classe Qt che prende un descrittore di file e fornisce un readReady() segnale, come ad es. QAbstractSocket , il ciclo di eventi eseguirà una funzione Python ogni volta che viene ricevuto un segnale (con un gestore) provocando l'esecuzione quasi istantanea del gestore del segnale senza bisogno di timer:

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. Modifica del layout/mappatura della tastiera sia sulla console (tty) che su X in modo indipendente da X/console?

  2. Quando si digita Ctrl-c in un terminale, perché il lavoro in primo piano non viene terminato fino al completamento?

  3. Qual è il modo migliore per inviare un segnale a tutti i membri di un gruppo di processi?

  4. Qual è il modo corretto per avviare un servizio mongod su Linux/OS X?

  5. Usa l'installazione predefinita di Python anziché l'installazione di Anaconda quando viene chiamata dal terminale

Qual è il modo corretto di installare jdk su Linux

Qual è il modo corretto per impedire agli utenti non root di emettere arresti o riavvii

Qual è il modo più veloce per spostare un milione di immagini da una directory all'altra in Linux?

Cosa determina esattamente se un lavoro in background viene interrotto quando la shell viene chiusa o terminata?

Qual è il modo più semplice per visualizzare i dati da stdout come grafico?

Qual è il modo corretto per aprire una serie di porte in iptables