linux 当从控制台(Ctrl-C)终止PyQt应用程序时,使其退出的正确方法是什么?

qltillow  于 2022-12-11  发布在  Linux
关注(0)|答案(9)|浏览(228)

当从控制台(Ctrl-C)终止PyQt应用程序时,使其退出的正确方法是什么?
目前(我没有做任何特别的处理unix信号的工作),我的PyQt应用程序忽略SIGINT(Ctrl+C)。我希望它表现良好,并在被杀死时退出。我应该怎么做呢?

hfyxw5xn

hfyxw5xn1#

17.4. signal — Set handlers for asynchronous events
尽管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_())

另一个可能的解决方案as pointed by LinearOrbitsignal.signal(signal.SIGINT, signal.SIG_DFL),但它不允许自定义处理程序。

dddzy1tm

dddzy1tm2#

如果您只是希望使用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上测试过(而且有效)。

kuarbcqp

kuarbcqp3#

18.8.1.1. Execution of Python signal handlers
Python信号处理程序不会在低级(C)信号处理程序中执行。相反,低级信号处理程序设置一个标志,告诉虚拟机在稍后的时间点(例如在下一条字节码指令)执行相应的Python信号处理程序。这会产生以下后果:
[...]
一个完全用C语言实现的长时间运行的计算(比如正则表达式匹配大量文本)可以不间断地运行任意长的时间,而不管接收到什么信号。当计算完成时,Python信号处理程序将被调用。
Qt事件循环是用C(++)实现的。这意味着,当它运行并且没有Python代码被调用时(例如,通过连接到Python插槽的Qt信号),信号被记录下来,但是Python信号处理程序没有被调用。

但是,从Python 2.6开始,在Python 3中,当使用signal.set_wakeup_fd()接收到带有处理程序的信号时,可以让Qt运行Python函数。

这是可能的,因为与文档相反,低级信号处理程序不仅为虚拟机设置一个标志,而且它还可以将一个字节写入set_wakeup_fd()设置的文件描述符中。Python 2写入一个NUL字节,Python 3写入信号编号。
因此,通过对一个Qt类进行子类化,该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_())
ovfsdjhp

ovfsdjhp4#

我找到了一种方法,其思想是迫使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_()
kqlmhetl

kqlmhetl5#

cg 909/ 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
myzjeezk

myzjeezk6#

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。

agyaoht7

agyaoht77#

您可以使用标准的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中,您可以释放所有资源(关闭所有数据库会话等)并轻轻关闭应用程序。
取自here的代码示例

v64noz0r

v64noz0r8#

我想我有一个更简单的解决方案:

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)

这只是告诉应用程序尝试关闭所有窗口,如果有一个未保存的文档,你的应用程序应该弹出一个保存或取消对话框,就像它被退出一样。
您可能还需要将QApplication信号lastWindowClosed()连接到slot quit(),以使应用程序在窗口关闭时实际退出。

shyt4zoc

shyt4zoc9#

您可以借鉴matplotlib的解决方案。
matplotlib有一个名为_maybe_allow_interrupt的函数隐藏在matplotlib.backends.backend_qt

from PyQt5 import QtWidgets
from matplotlib.backends.backend_qt import _maybe_allow_interrupt
import sys

app = QtWidgets.QApplication(sys.argv)
mw = QtWidgets.QMainWindow()
mw.show()
with _maybe_allow_interrupt(app):
    app.exec()

当然,由于这不是一个公共函数,它可能会在matplotlib的未来版本中改变或消失,所以这更像是一个“快速而肮脏”的解决方案。

相关问题