GNU/Linux >> Tutoriales Linux >  >> Linux

¿Cuál es la forma correcta de hacer que mi aplicación PyQt se cierre cuando se elimina desde la consola (Ctrl-C)?

17.4. señal — Establecer controladores para eventos asíncronos

Aunque los controladores de señales de Python se llaman de forma asíncrona en lo que respecta al usuario de Python, solo pueden ocurrir entre las instrucciones "atómicas" del intérprete de Python. Esto significa que las señales que llegan durante largos cálculos implementados puramente en C (como coincidencias de expresiones regulares en grandes cuerpos de texto) pueden retrasarse por una cantidad de tiempo arbitraria.

Eso significa que Python no puede manejar señales mientras se ejecuta el ciclo de eventos Qt. Solo cuando se ejecuta el intérprete de Python (cuando se cierra QApplication o cuando se llama a una función de Python desde Qt), se llamará al controlador de señales.

Una solución es usar un QTimer para permitir que el intérprete se ejecute de vez en cuando.

Tenga en cuenta que, en el código siguiente, si no hay ventanas abiertas, la aplicación se cerrará después del cuadro de mensaje independientemente de la elección del usuario porque QApplication.quitOnLastWindowClosed() ==True. Este comportamiento se puede cambiar.

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

Otra posible solución, como señala LinearOrbit, es signal.signal(signal.SIGINT, signal.SIG_DFL) , pero no permite controladores personalizados.


Si simplemente desea que ctrl-c cierre la aplicación, sin ser "amable"/elegante al respecto, entonces desde http://www.mail-archive.com/[email protected]/msg13758.html, puede usar esto:

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

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

Aparentemente esto funciona en Linux, Windows y OSX. Hasta ahora solo lo he probado en Linux (y funciona).


18.8.1.1. Ejecución de manejadores de señales de Python

Un controlador de señales de Python no se ejecuta dentro del controlador de señales de bajo nivel (C). En su lugar, el controlador de señal de bajo nivel establece un indicador que le dice a la máquina virtual que ejecute el controlador de señal de Python correspondiente en un punto posterior (por ejemplo, en la siguiente instrucción de código de bytes). Esto tiene consecuencias:
[...]
Un cálculo de ejecución prolongada implementado únicamente en C (como la coincidencia de expresiones regulares en un gran cuerpo de texto) puede ejecutarse sin interrupciones durante un período de tiempo arbitrario, independientemente de las señales recibidas. Se llamará a los controladores de señales de Python cuando finalice el cálculo.

El bucle de eventos de Qt se implementa en C(++). Eso significa que, mientras se ejecuta y no se llama a ningún código de Python (por ejemplo, mediante una señal Qt conectada a una ranura de Python), las señales se anotan, pero no se llama a los controladores de señales de Python.

Pero , desde Python 2.6 y en Python 3 puede hacer que Qt ejecute una función de Python cuando se recibe una señal con un controlador usando signal.set_wakeup_fd() .

Esto es posible porque, contrariamente a la documentación, el controlador de señal de bajo nivel no solo establece un indicador para la máquina virtual, sino que también puede escribir un byte en el descriptor de archivo establecido por set_wakeup_fd() . Python 2 escribe un byte NUL, Python 3 escribe el número de señal.

Entonces, al subclasificar una clase Qt que toma un descriptor de archivo y proporciona un readReady() señal, como p. QAbstractSocket , el bucle de eventos ejecutará una función de Python cada vez que se reciba una señal (con un controlador), lo que hará que el controlador de señales se ejecute casi instantáneamente sin necesidad de temporizadores:

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. Usando Reddit desde la consola en 2020

  2. ¿Cambiar la distribución/asignación del teclado tanto en la consola (tty) como en X de una manera agnóstica de X/consola?

  3. Al escribir Ctrl-c en una terminal, ¿por qué el trabajo en primer plano no finaliza hasta que se completa?

  4. Python:¿la forma correcta de actualizar Pip en Stretch Debian?

  5. Permitir que Ctrl-C interrumpa una extensión C de Python

¿Cuál es la forma correcta de instalar jdk en Linux?

¿Cuál es la forma más fácil de detectar pulsaciones de teclas en Python 3 en una máquina Linux?

¿Cuál es la forma correcta de evitar que los usuarios no root emitan apagados o reinicios?

¿Cuál es la mejor manera de hacer una unidad flash con muchas cosas de arranque?

¿Cuál es la forma más fácil de visualizar datos de stdout como un gráfico?

¿Cuál es la forma correcta de ver la velocidad de su CPU en Linux?