17.4. signal — 非同期イベントのハンドラーを設定する
Python ユーザーに関する限り、Python シグナル ハンドラーは非同期的に呼び出されますが、Python インタープリターの「アトミック」命令間でのみ発生する可能性があります。これは、純粋に C で実装された長い計算 (大量のテキストでの正規表現の一致など) 中に到着するシグナルが、任意の時間遅延する可能性があることを意味します。
つまり、Qt イベント ループの実行中は、Python はシグナルを処理できません。 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_())
LinearOrbit が指摘する別の可能な解決策は、 signal.signal(signal.SIGINT, signal.SIG_DFL)
です。 、ただし、カスタム ハンドラーは許可されません。
単純に 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 でしかテストしていません (そして動作します)。
<ブロック引用>
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()
によって設定されたファイル記述子にバイトを書き込む可能性があるため、これが可能です。 . 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_())