Подтвердить что ты не робот

Каков правильный способ заставить приложение PyQt покинуть консоль (Ctrl-C)?

Каков правильный способ заставить приложение PyQt выйти из консоли (Ctrl-C)?

В настоящее время (я не делал ничего особенного для обработки сигналов unix), мое приложение PyQt игнорирует SIGINT (Ctrl + C). Я хочу, чтобы он хорошо себя ведет и уходит, когда его убивают. Как мне это сделать?

4b9b3361

Ответ 1

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 __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), но оно не допускает пользовательских обработчиков.

Ответ 2

Если вы просто хотите, чтобы 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

Я нашел способ сделать это. Идея состоит в том, чтобы заставить qt обрабатывать события достаточно часто и в callabe 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_()

Ответ 4

18.8.1.1. Выполнение обработчиков сигналов Python

Обработчик сигнала Python не выполняется в низкоуровневом (C) сигнальном обработчике. Вместо этого низкоуровневый обработчик сигналов устанавливает флаг, который сообщает виртуальной машине выполнить соответствующий обработчик сигнала Python в более поздней точке (например, в следующей инструкции по байт-коду). Это имеет последствия:
[...]
Долгосрочный расчет, реализованный исключительно в C (например, согласование регулярных выражений на большом тексте), может работать без прерывания в течение произвольного промежутка времени независимо от полученных сигналов. Обработчики сигналов Python будут вызываться, когда вычисление завершается.

Цикл событий Qt реализован в C (++). Это означает, что пока он работает и не вызывается код Python (например, с помощью сигнала Qt, подключенного к слоту Python), сигналы отмечены, но обработчики сигналов Python не вызываются.

Но, поскольку Python 2.6 и в Python 3 вы можете заставить Qt запускать функцию Python, когда сигнал с обработчиком получен с помощью signal.set_wakeup_fd().

Это возможно, потому что, в отличие от документации, низкоуровневый обработчик сигналов не только устанавливает флаг для виртуальной машины, но также может записывать байт в дескриптор файла, установленный set_wakeup_fd(). Python 2 записывает нулевой байт, Python 3 записывает номер сигнала.

Итак, подклассифицируя класс 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_())

Ответ 5

Вы можете использовать стандартный механизм обработки сигналов unix-сигналов python:

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 и т.д.) и аккуратно закрыть приложение.

Пример кода, взятый из здесь

Ответ 6

Я думаю, что у меня есть более простое решение:

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(), чтобы заставить приложение фактически выйти, когда окна закрыты.

Ответ 7

Ответ от Артура Гаспара работал у меня, когда окно терминала было в центре внимания, но не работало, когда графический интерфейс был в фокусе. Чтобы закрыть мой графический интерфейс (который наследуется от QWidget), мне пришлось определить следующую функцию в классе:

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

Проверка, чтобы убедиться, что ключ события равен 67, необходимо нажать "c". Затем проверка модификаторов событий определяет, был ли нажат ctrl при выпуске 'c'.

Ответ 8

Если кому-то интересно, вы можете посмотреть, как мы сделали это для matplotlib на основе cg909. Очень хороший ответ:

https://github.com/matplotlib/matplotlib/pull/13306

class allow_interrupt():

    def __init__(self, interrupted_qobject):
        QAS = QtNetwork.QAbstractSocket
        self.qt_socket = QAS(QAS.TcpSocket, qApp)
        self.old_fd = None
        self.old_sigint_handler = None
        self.interrupt_caught = False
        self.interrupted_qobject = interrupted_qobject
        # Create a socket pair
        self.wsock, self.rsock = socket.socketpair()
        # Let Qt listen on the one end
        self.qt_socket.setSocketDescriptor(self.rsock.fileno())
        self.wsock.setblocking(False)
        # First Python code executed gets any exception from
        # the signal handler, so add a dummy handler first
        self.qt_socket.readyRead.connect(lambda: None)
        # Second handler does the real handling
        self.qt_socket.readyRead.connect(self._readSignal)

    def __enter__(self):
        # And let Python write on the other end
        self.old_fd = signal.set_wakeup_fd(self.wsock.fileno())
        self.old_sigint_handler = signal.getsignal(signal.SIGINT)
        signal.signal(signal.SIGINT, self._handle)

    def __exit__(self, type, val, traceback):
        signal.set_wakeup_fd(self.old_fd)
        signal.signal(signal.SIGINT, self.old_sigint_handler)
        self.wsock.close()
        self.rsock.close()
        self.qt_socket.abort()
        if self.interrupt_caught:
            self.old_sigint_handler()

    def _readSignal(self):
        # Read the written byte.
        # Note: readyRead is blocked from occurring again until readData()
        # was called, so call it, even if you don't need the value.
        data = self.qt_socket.readData(1)

    def _handle(self, *args):
        self.interrupt_caught = True
        if self.interrupted_qobject is qApp:
            pyplot.close()
        self.interrupted_qobject.quit()