web-dev-qa-db-ja.com

コンソール(Ctrl-C)から強制終了したときにPyQtアプリケーションを終了させる正しい方法は何ですか?

コンソール(Ctrl-C)から強制終了したときにPyQtアプリケーションを終了させる正しい方法は何ですか?

現在(UNIXシグナルを処理するために特別なことは何もしていません)、私のPyQtアプリケーションはSIGINT(Ctrl + C)を無視します。私はそれがうまく動作し、それが殺されたら終了することを望んでいます。どうすればいいですか?

64
static_rtti

17.4。signal —非同期イベントのハンドラーを設定する

Pythonシグナルハンドラーは、Pythonユーザーに関する限り、非同期で呼び出されますが、Pythonインタープリター。これは、純粋にCで実装された長い計算中に到着する信号(テキストの大きな本文での正規表現の一致など)が任意の時間遅延する可能性があることを意味します。

つまり、PythonはQtイベントループの実行中に信号を処理できません。Pythonインタープリターの実行時(QApplicationの終了時、またはPython関数はQtから呼び出されます)シグナルハンドラーが呼び出されます。

解決策は、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 __== "__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)ですが、カスタムハンドラーを許可しません。

43
Artur Gaspar

単にctrl-cを使用してアプリケーションを閉じたい場合(「いい」/優雅にならずに)- http://www.mail-archive.com/[email protected] 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でしかテストしていません(動作します)。

39
LinearOrbit

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はシグナル番号を書き込みます。

そのため、ファイル記述子を取り、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_())
7
cg909

これを行う方法を見つけました。アイデアは、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_()
5
parkouss

私はもっ​​と簡単な解決策があると思う:

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()に接続して、ウィンドウが閉じられたときにアプリケーションを実際に終了させる必要がある場合もあります。

1
xioxox

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が押されていたかどうかを判断します。

1
qwerty9967

標準の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すべてのリソースを解放し(すべてのdbセッションを閉じるなど)、アプリケーションを穏やかに閉じることができます。

here からのコード例

1
karolx