簡體   English   中英

從控制台 (Ctrl-C) 終止我的 PyQt 應用程序時退出的正確方法是什么?

[英]What is the correct way to make my PyQt application quit when killed from the console (Ctrl-C)?

從控制台 (Ctrl-C) 終止我的 PyQt 應用程序時退出的正確方法是什么?

目前(我沒有做任何特別的事情來處理 unix 信號),我的 PyQt 應用程序忽略了 SIGINT (Ctrl+C)。 我希望它表現良好並在它被殺死時退出。 我該怎么做?

17.4. 信號 - 為異步事件設置處理程序

盡管就 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_())

LinearOrbit 指出的另一個可能的解決方案是signal.signal(signal.SIGINT, signal.SIG_DFL) signal.SIGINT , 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 上測試過這個(並且它有效)。

18.8.1.1. Python 信號處理程序的執行

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()設置的set_wakeup_fd() Python 2 寫入一個 NUL 字節,Python 3 寫入信號編號。

因此,通過readReady()采用文件描述符並提供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_())

我找到了一種方法來做到這一點。 這個想法是強制 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_()

當終端窗口處於焦點時,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() 以使應用程序在窗口關閉時實際退出。

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

您可以搭載 matplotlib 的解決方案。

matplotlib 在 matplotlib.backends.backend_qt 中隱藏了一個名為_maybe_allow_interruptmatplotlib.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()

當然,由於這不是公共 function,它可能會在 matplotlib 的未來版本中更改或消失,因此這更像是一個“快速而骯臟”的解決方案。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM