Art*_*par 43
虽然就Python用户而言,Python信号处理程序是异步调用的,但它们只能出现在Python解释器的"原子"指令之间.这意味着在纯C中实现的长计算期间到达的信号(例如大文本上的正则表达式匹配)可能会延迟一段任意时间.
这意味着Python在Qt事件循环运行时无法处理信号.只有当Python解释器运行时(当QApplication退出时,或者从Qt调用Python函数时),才会调用信号处理程序.
解决方案是使用QTimer让解释器不时运行.
请注意,在下面的代码中,如果没有打开的窗口,应用程序将在消息框之后退出,而不管用户的选择如何,因为QApplication.quitOnLastWindowClosed()== True.可以更改此行为.
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_())
Run Code Online (Sandbox Code Playgroud)
LinearOrbit指出的另一种可能的解决方案是signal.signal(signal.SIGINT, signal.SIG_DFL),但它不允许自定义处理程序.
小智 39
如果你只是想有CTRL-C关闭应用程序-而不"好" /曼妙一下-然后从HTTP://www.mail- archive.com/pyqt@riverbankcomputing.com/msg13758.html,您可以使用这个:
import signal
signal.signal(signal.SIGINT, signal.SIG_DFL)
import sys
from PyQt4.QtCore import QCoreApplication
app = QCoreApplication(sys.argv)
app.exec_()
Run Code Online (Sandbox Code Playgroud)
显然这适用于Linux,Windows和OSX - 到目前为止我只在Linux上测试过它(并且它可以工作).
Python信号处理程序不会在低级(C)信号处理程序中执行.相反,低级信号处理程序设置一个标志,告诉虚拟机稍后执行相应的Python信号处理程序(例如在下一个字节码指令处).这具有以下后果:
[...]
纯粹在C中实现的长时间运行计算(例如在大量文本上进行正则表达式匹配)可以在任意时间内不间断地运行,无论接收到任何信号.计算完成后将调用Python信号处理程序.
Qt事件循环用C(++)实现.这意味着,当它运行并且没有调用Python代码时(例如,通过连接到Python插槽的Qt信号),会记录信号,但不会调用Python信号处理程序.
但是,从Python 2.6和Python 3开始,当接收到带有处理程序的信号时,可以使Qt运行Python函数signal.set_wakeup_fd().
这是可能的,因为与文档相反,低级信号处理程序不仅为虚拟机设置了一个标志,而且还可以将一个字节写入设置的文件描述符中set_wakeup_fd().Python 2写入一个NUL字节,Python 3写入信号编号.
所以,通过继承Qt的类,它需要一个文件描述符,并提供了一个readReady()信号,如例如QAbstractSocket,事件循环将每次执行一个Python函数的信号(与控制器)接收使信号处理程序,而不需要计时器执行几乎瞬时:
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_())
Run Code Online (Sandbox Code Playgroud)
小智 6
cg909 / Michael Herrmann 的异步方法对于替换计时器来说非常有趣。因此,这是一个简化版本,它也使用 socket.socketpair (SOCK_STREAM) 的默认类型。
class SignalWatchdog(QtNetwork.QAbstractSocket):
def __init__(self):
""" Propagates system signals from Python to QEventLoop """
super().__init__(QtNetwork.QAbstractSocket.SctpSocket, None)
self.writer, self.reader = socket.socketpair()
self.writer.setblocking(False)
signal.set_wakeup_fd(self.writer.fileno()) # Python hook
self.setSocketDescriptor(self.reader.fileno()) # Qt hook
self.readyRead.connect(lambda: None) # Dummy function call
Run Code Online (Sandbox Code Playgroud)
小智 5
我找到了一种方法来做到这一点.我们的想法是强制qt经常处理事件,并在python callabe中捕获SIGINT信号.
import signal, sys
from PyQt4.QtGui import QApplication, QWidget # also works with PySide
# You HAVE TO reimplement QApplication.event, otherwise it does not work.
# I believe that you need some python callable to catch the signal
# or KeyboardInterrupt exception.
class Application(QApplication):
def event(self, e):
return QApplication.event(self, e)
app = Application(sys.argv)
# Connect your cleanup function to signal.SIGINT
signal.signal(signal.SIGINT, lambda *a: app.quit())
# And start a timer to call Application.event repeatedly.
# You can change the timer parameter as you like.
app.startTimer(200)
w = QWidget()
w.show()
app.exec_()
Run Code Online (Sandbox Code Playgroud)