从控制台(Ctrl-C)中杀死PyQt应用程序时退出的正确方法是什么?

sta*_*tti 65 python linux qt signals pyqt

从控制台(Ctrl-C)中杀死PyQt应用程序时退出的正确方法是什么?

目前(我没有特别处理unix信号),我的PyQt应用程序忽略了SIGINT(Ctrl + C).我希望它表现得很好,并在它被杀死时退出.我该怎么办?

Art*_*par 43

17.4.signal - 设置异步事件的处理程序

虽然就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),但它不允许自定义处理程序.

  • 似乎不起作用... Qt 似乎在我之前捕获了异常。 (2认同)
  • 你的第二个解决方案有效......有点儿.当我按下Ctrl-C时,应用程序不会像预期的那样立即终止,而是等待焦点恢复到应用程序. (2认同)

小智 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上测试过它(并且它可以工作).

  • 这项工作,但要注意它将绕过你想要做的任何清洁,如finally块中的调用. (3认同)

cg9*_*909 7

18.8.1.1.执行Python信号处理程序

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)