18.8.1.1. Execution of Python signal handlers
A Python signal handler does not get executed inside the low-level (C) signal handler. Instead, the low-level signal handler sets a flag which tells the virtual machine to execute the corresponding Python signal handler at a later point(for example at the next bytecode instruction). This has consequences:
[...]
A long-running calculation implemented purely in C (such as regular expression matching on a large body of text) may run uninterrupted for an arbitrary amount of time, regardless of any signals received. The Python signal handlers will be called when the calculation finishes.
El ciclo de eventos Qt se implementa en C (++). Eso significa que mientras se ejecuta y no se llama ningún código Python (por ejemplo, mediante una señal Qt conectada a una ranura Python), las señales se anotan, pero los manejadores de señal Python no son llamados.
Pero, ya que Python 2.6 y en Python 3 puede causar Qt para ejecutar una función de Python cuando una señal con un controlador se recibe mediante signal.set_wakeup_fd()
.
Esto es posible porque, contrariamente a la documentación, el manejador 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 en set_wakeup_fd()
. Python 2 escribe un byte NUL, Python 3 escribe el número de señal.
Subclase una clase Qt que toma un descriptor de archivo y proporciona una señal readReady()
, como p. Ej. QAbstractSocket
, el bucle de eventos se ejecutará una función de Python cada vez que una señal (con un controlador) está recibida haciendo que el manejador de señales para ejecutar casi instantánea 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_())
nunca he entendido por qué casi todos los script en Python en el mundo se detiene con un control + c a excepción de las aplicaciones pyqt. Sin duda hay una buena razón para eso, pero al final es muy molesto. – tokland
@tokland: resolvamos esto una vez para siempre :) –
parece ser un problema de diseño: http://www.mail-archive.com/[email protected]/msg13757.html. Cualquier solución que implique excepciones o similar solo se siente hacky :-( – tokland