2011-02-08 20 views
56

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

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

+5

は私がなぜほぼすべてのPythonスクリプトを理解したことがありませんpyqtのアプリケーションを除いて、コントロール+ cで停止します。確かにそれには正当な理由がありますが、結局それは非常に迷惑です。 – tokland

+0

@tokland:これはすべて一度解決しましょう:) –

+1

これは設計上の問題です:http://www.mail-archive.com/[email protected]/msg13757.html。例外や同様の問題を含むあらゆる解決策は、ハッキーな感じです:-( – tokland

答えて

38

17.4. signal — Set handlers for asynchronous events

Although Python signal handlers are called asynchronously as far as the Python user is concerned, they can only occur between the “atomic” instructions of the Python interpreter. This means that signals arriving during long calculations implemented purely in C (such as regular expression matches on large bodies of text) may be delayed for an arbitrary amount of time.

から採取しました。 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 __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_()) 

もう1つの可能な解決策、as pointed by LinearOrbitは、signal.signal(signal.SIGINT, signal.SIG_DFL)ですが、それはカスタムハンドラを許可していません。私は簡単な解決策を持っていると思う

+0

動作していないようです...できるだけ先にQtが例外をキャッチしているようです。 –

+2

2番目のソリューションが動作します...ちょっとCtrl-Cを押すと、アプリケーションはすぐに終了しません –

+0

とにかく、あなたの答えをありがとう、私は誰もより良い答えを与えていない場合は、より具体的な質問をしようとします。 –

2

あなたはunixのメカニズムを扱う信号の標準のpythonを使用することができます。signal_handlerにあなたはすべてのリソース(すべて閉じデシベルのセッションなど)を解放し、優しくapplictionを閉じることができます

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 

コード例では、Qtのイベントループの実行中にPythonが信号を扱うことができないことを意味here

+0

少なくとも私の問題を解決するには、あなたの例では、あなたの例では、あなたはしない... –

+0

アプリケーションとメインウィンドウが作成された後、app._exec()を呼び出す前にこれを配置する場合、実際にあなたのアプリ –

2

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

私はあなたのソリューションをテストしていないが、私はかなり上のソリューションの1つの同じ問題に苦しんでいると確信しています: –

+0

制御がパイソンインタプリタに戻る前にすなわち、アプリケーションがスリープから復帰する前にこれは、アプリケーションが終了するためにフォーカスを取り戻すために待機する必要があることを意味します。私には容認できない。 –

+0

若干の反省の後、私はアーサー・ガスパルの最後のソリューションを実装し、デバッグ時に簡単に無効にするシステムと考えています。 –

34

あなたは、単にCtrl + Cキーのアプリケーションを閉じていたい場合 - それについての優雅な/「素敵」されることなく - そしてhttp://www.mail-archive.com/[email protected]/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上でこれをテストしています(それは動作します)。

+3

この作品は、最終的なブロックでの呼び出しなど、あなたがやりたがっているすべてのクリーニングをバイパスすることに注意してください。 –

5

これを行う方法が見つかりました。この考え方は、qtがイベントを十分に頻繁に処理し、Pythonコールバブで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_() 
+0

私はこのように接続してリターンコードも 'signal.signal(signal.SIGINT、lambda * a:app。exit(-2)) ' – dashesy

0

Artur Gasparの答えは、ターミナルウィンドウにフォーカスがあったときに働いていましたが、GUIにフォーカスがあっても機能しませんでした。

def keyPressEvent(self,event): 
    if event.key() == 67 and (event.modifiers() & QtCore.Qt.ControlModifier): 
     sigint_handler() 

イベントキーは、67「のC」が押されたことを確認していることを確認するチェック:ために私のGUIは、私はクラスに次の関数を定義する必要がありました(QWidgetから継承する)閉じるように取得します。次に、イベント修飾子をチェックすることで、 'c'が解放されたときにctrlが押されているかどうかが判断されます。

5

18.8.1.1. Execution of Python signal handlers

A Python signal handler does not get executed inside the low-level (C) signal handler. Instead, the low-level signal handler sets a flag which tells the virtual machine to execute the corresponding Python signal handler at a later point(for example at the next bytecode instruction). This has consequences:
[...]
A long-running calculation implemented purely in C (such as regular expression matching on a large body of text) may run uninterrupted for an arbitrary amount of time, regardless of any signals received. The Python signal handlers will be called when the calculation finishes.

QtイベントループはC(++)で実装されています。つまり、実行中にPythonコードが呼び出されない(たとえば、Pythonスロットに接続されたQtシグナルによって)シグナルは通知されますが、Pythonシグナルハンドラは呼び出されません。

しかし、あなたはハンドラを持つ信号がsignal.set_wakeup_fd()を使用して受信されたときにQtはPythonの関数を実行する可能性がありますはPython 2.6以降とPython 3に。

ドキュメントとは異なり、低レベルシグナルハンドラは仮想マシンのフラグを設定するだけでなく、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_()) 
+0

残念ながら、これは' socket.socketpair'が存在しないため、ウィンドウでは機能しません。 (私は 'backports.socketpair'を試しましたが、これはうまくいきません)。 – coldfix

+0

Windowsソケットや他のファイルのようなハンドルは別々に扱われるようですので、おそらくソケットを使用しない別の構造が必要です。私はWindowsを使用していないので、何がうまくいくかテストできません。 Python 3.5ソケットがサポートされているようです(https://docs.python.org/3/library/signal.html#signal.set_wakeup_fdを参照)。 – cg909

+0

macOS上でPython 3.5.3でこれを試してみると、 'ValueError:fd 10は非ブロックモードでなければなりません 'というメッセージが表示されます。 –

関連する問題