Linux 从控制台(Ctrl-C)杀死时,让我的 PyQt 应用程序退出的正确方法是什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4938723/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What is the correct way to make my PyQt application quit when killed from the console (Ctrl-C)?
提问by static_rtti
What is the correct way to make my PyQt application quit when killed from the console (Ctrl-C)?
从控制台(Ctrl-C)杀死时,让我的 PyQt 应用程序退出的正确方法是什么?
Currently (I have done nothing special to handle unix signals), my PyQt application ignores SIGINT (Ctrl+C). I want it to behave nicely and quit when it is killed. How should I do that?
目前(我没有做任何特殊的事情来处理 unix 信号),我的 PyQt 应用程序忽略了 SIGINT (Ctrl+C)。我希望它表现得很好并在它被杀死时退出。我该怎么做?
采纳答案by Artur Gaspar
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 用户而言,Python 信号处理程序是异步调用的,但它们只能发生在 Python 解释器的“原子”指令之间。这意味着在纯 C 实现的长时间计算中到达的信号(例如大文本体上的正则表达式匹配)可能会延迟任意时间。
That means Python cannot handle signals while the Qt event loop is running. Only when the Python interpreter run (when the QApplication quits, or when a Python function is called from Qt) the signal handler will be called.
这意味着 Python 无法在 Qt 事件循环运行时处理信号。只有当 Python 解释器运行时(当 QApplication 退出时,或者当从 Qt 调用 Python 函数时)才会调用信号处理程序。
A solution is to use a QTimer to let the interpreter run from time to time.
一个解决方案是使用 QTimer 让解释器不时运行。
Note that, in the code below, if there are no open windows, the application will quit after the message box regardless of the user's choice because QApplication.quitOnLastWindowClosed() == True. This behaviour can be changed.
请注意,在下面的代码中,如果没有打开的窗口,则无论用户选择如何,应用程序都会在消息框之后退出,因为 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_())
Another possible solution, as pointed by LinearOrbit, is signal.signal(signal.SIGINT, signal.SIG_DFL)
, but it doesn't allow custom handlers.
LinearOrbit 指出的另一个可能的解决方案是signal.signal(signal.SIGINT, signal.SIG_DFL)
,但它不允许自定义处理程序。
回答by karolx
You can use the standard python unix signals handling mechanism:
您可以使用标准的 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
where in signal_handler
you can free all resources (close all db sessions etc) and gently close your appliction.
signal_handler
您可以在其中释放所有资源(关闭所有数据库会话等)并轻轻关闭您的应用程序。
Code example taken from here
代码示例取自此处
回答by xioxox
I think I have a simpler solution:
我想我有一个更简单的解决方案:
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)
This just tells the application to try to close all windows if ctrl+c is pressed. If there is an unsaved document, your app should pop up a save or cancel dialog box as if it were exited.
这只是告诉应用程序在按下 ctrl+c 时尝试关闭所有窗口。如果有未保存的文档,你的应用程序应该弹出一个保存或取消对话框,就像它已经退出一样。
You may also need to connect the QApplication signal lastWindowClosed() to the slot quit() to get the application to actually exit when the windows are closed.
您可能还需要将 QApplication 信号 lastWindowClosed() 连接到插槽 quit() 以使应用程序在窗口关闭时实际退出。
回答by LinearOrbit
If you simply wish to have ctrl-c close the application - without being "nice"/graceful about it - then from http://www.mail-archive.com/[email protected]/msg13758.html, you can use this:
如果你只是想有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_()
Apparently this works on Linux, Windows and OSX - I have only tested this on Linux so far (and it works).
显然这适用于 Linux、Windows 和 OSX - 到目前为止我只在 Linux 上测试过这个(并且它有效)。
回答by parkouss
I found a way to do this. The idea is to force qt to process events often enough and in a python callabe to catch the SIGINT signal.
我找到了一种方法来做到这一点。这个想法是强制 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_()
回答by qwerty9967
The answer from Artur Gaspar worked for me when the terminal window was in focus, but would not work when the GUI was in focus. In order to get my GUI to close (which inherits from QWidget) I had to define the following function in the class:
当终端窗口处于焦点时,Artur Gaspar 的回答对我有用,但在 GUI 处于焦点时不起作用。为了让我的 GUI 关闭(从 QWidget 继承),我必须在类中定义以下函数:
def keyPressEvent(self,event):
if event.key() == 67 and (event.modifiers() & QtCore.Qt.ControlModifier):
sigint_handler()
Checking to make sure that the event key is 67 makes sure that 'c' was pressed. Then checking the event modifiers determines whether ctrl was being pressed when 'c' was released.
检查以确保事件键为 67 以确保按下了“c”。然后检查事件修饰符确定在释放 'c' 时是否按下了 ctrl。
回答by cg909
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.
Python 信号处理程序不会在低级 (C) 信号处理程序内执行。相反,低级信号处理程序设置一个标志,告诉虚拟机在稍后的时间点(例如在下一个字节码指令处)执行相应的 Python 信号处理程序。这会产生以下后果:
[...]
纯粹用 C 实现的长时间运行的计算(例如对大量文本进行正则表达式匹配)可能会不间断地运行任意时间,而不管接收到任何信号。计算完成后将调用 Python 信号处理程序。
The Qt event loop is implemented in C(++). That means, that while it runs and no Python code is called (eg. by a Qt signal connected to a Python slot), the signals are noted, but the Python signal handlers aren't called.
Qt 事件循环是用 C(++) 实现的。这意味着,当它运行并且没有调用 Python 代码时(例如,通过连接到 Python 插槽的 Qt 信号),会记录信号,但不会调用 Python 信号处理程序。
But, since Python 2.6 and in Python 3 you can cause Qt to run a Python function when a signal with a handler is received using signal.set_wakeup_fd()
.
但是,从 Python 2.6 和 Python 3 开始,当使用signal.set_wakeup_fd()
.
This is possible, because, contrary to the documentation, the low-level signal handler doesn't only set a flag for the virtual machine, but it may also write a byte into the file descriptor set by set_wakeup_fd()
. Python 2 writes a NUL byte, Python 3 writes the signal number.
这是可能的,因为与文档相反,低级信号处理程序不仅为虚拟机设置了一个标志,而且还可能将一个字节写入由set_wakeup_fd()
. Python 2 写入一个 NUL 字节,Python 3 写入信号编号。
So by subclassing a Qt class that takes a file descriptor and provides a readReady()
signal, like e.g. QAbstractSocket
, the event loop will execute a Python function every time a signal (with a handler) is received causing the signal handler to execute nearly instantaneous without need for timers:
因此,通过子类化一个 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_())