使我的PyQt应用程序从控制台中退出时(Ctrl-C),退出的正确方法是什么?
当前(我在处理unix信号方面没有做任何特别的事情),我的PyQt应用程序忽略了SIGINT(Ctrl + C)。我希望它表现良好并在被杀死时退出。我该怎么办?
使我的PyQt应用程序从控制台中退出时(Ctrl-C),退出的正确方法是什么?
当前(我在处理unix信号方面没有做任何特别的事情),我的PyQt应用程序忽略了SIGINT(Ctrl + C)。我希望它表现良好并在被杀死时退出。我该怎么办?
Answers:
尽管就Python用户而言,Python信号处理程序是异步调用的,但它们只能出现在Python解释器的“原子”指令之间。这意味着纯粹在C语言中执行的长计算期间到达的信号(例如,大文本正文中的正则表达式匹配)可能会延迟任意时间。
这意味着在Qt事件循环运行时,Python无法处理信号。仅当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_())
如LinearOrbit所指出的,另一个可能的解决方案是signal.signal(signal.SIGINT, signal.SIG_DFL)
,但是它不允许自定义处理程序。
如果您只是希望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_()
显然,这可以在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写信号号。
因此,通过对接受文件描述符并提供readReady()
信号的Qt类进行子类化,例如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_())
socket.socketpair
。(我尝试过backports.socketpair
,但这也不起作用)。
ValueError: the fd 10 must be in non-blocking mode
在macOS上使用Python 3.5.3尝试时遇到问题。
self.wsock.setblocking(False)
解决了我先前的推荐中提到的问题。
我找到了一种方法。这个想法是迫使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_()
signal.signal(signal.SIGINT, lambda *a: app.exit(-2))
当终端窗口处于焦点位置时,Artur Gaspar的答案对我有用,但是当GUI处于焦点位置时,该答案不起作用。为了关闭我的GUI(继承自QWidget),我必须在类中定义以下函数:
def keyPressEvent(self,event):
if event.key() == 67 and (event.modifiers() & QtCore.Qt.ControlModifier):
sigint_handler()
检查以确保事件键为67,以确保已按下“ c”。然后检查事件修饰符,确定释放“ c”时是否按下了ctrl。
您可以使用标准的python unix信号处理机制:
import signal
import sys
def signal_handler(signal, frame):
print 'You pressed Ctrl+C!'
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
print 'Press Ctrl+C'
while 1:
continue
signal_handler
您可以在其中释放所有资源(关闭所有数据库会话等)并轻轻关闭您的应用程序。
从此处获取的代码示例
我认为我有一个更简单的解决方案:
import signal
import PyQt4.QtGui
def handleIntSignal(signum, frame):
'''Ask app to close if Ctrl+C is pressed.'''
PyQt4.QtGui.qApp.closeAllWindows()
signal.signal(signal.SIGINT, handleIntSignal)
这只是告诉应用程序,如果按下ctrl + c,则尝试关闭所有窗口。如果有未保存的文档,您的应用程序应弹出一个保存或取消对话框,就像退出该对话框一样。
您可能还需要将QApplication信号lastWindowClosed()连接到插槽quit(),以使应用程序在关闭窗口时实际退出。