Как правильно обрабатывать SIGINT для закрытия файлов/соединений

Я хочу реализовать правильную обработку SIGINT в своем сценарии, который открывает несколько файлов и соединение с базой данных. Они должны быть закрыты, если скрипт нажат CTRL+C или каким-либо другим образом прерван.

Раньше я использовал исключение KeyboardInterrupt для перехвата CTRL+C, там я проверял, определены ли файлы/соединения, если да, закройте их, затем выйдите.

Это действительно питонический способ сделать это, или лучше использовать обработчики сигналов? например

import signal, sys, time

def handler(signum, frame):
    print("..kthxbye")
    sys.exit(1)

def main():
    signal.signal(signal.SIGINT, handler)
    i = 0
    while True:
        print(i)
        i += 1
        time.sleep(1)

if __name__ == "__main__":
    main()

Мне это кажется более чистым, но я не знаю, как передать имена файлов или соединения с базой данных обработчику.


person Daedalus Mythos    schedule 15.10.2014    source источник


Ответы (2)


Я бы предпочел поймать исключение KeyboardInterrupt в основном потоке. KeyboardInterrupt является результатом обработчика SIGINT по умолчанию для python. Обработчик исключения KeyboardInterrupt является гораздо более безопасным/дружественным контекстом, чем тот, в котором вы находитесь при непосредственном перехвате SIGINT.

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        cleanup()

РЕДАКТИРОВАТЬ: Вот как разделить переменные (состояние) между двумя методами:

Процедурный:

import sys, time

class SharedState:
    def __init__(self):
        self.var0 = 42
        self.var1 = 'string'

# method 1
shared_variable = 'woof woof'

# method 2: avoiding global declarations in functions
shared_state = SharedState()

def main():
    # In order to write a global variable you need a global
    # declaration otherwise the assignment would create a
    # local variable

    global shared_variable
    shared_variable = 5

    shared_state.var0 = 10

    time.sleep(10)

def cleanup():
    print shared_variable
    print shared_state.var0
    sys.exit(1)

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        cleanup()

Объектно-ориентированный (мое предпочтение):

import sys, time

# method 3: object oriented programming
class Program:
    def __init__(self):
        self.var0 = 42
        self.var1 = 'string'

    def main(self):
        self.var0 = 5
        self.var1 = 'woof woof'
        time.sleep(10)

    def cleanup(self):
        # both main and cleanup can access the member
        # variables of this class
        print self.var0
        print self.var1
        sys.exit(1)

    def execute(self):
        try:
            self.main()
        except KeyboardInterrupt:
            self.cleanup()

if __name__ == '__main__':
    Program().execute()
person pasztorpisti    schedule 15.10.2014
comment
Спасибо за твой ответ. поэтому мне лучше обернуть несколько сотен строк main в try/кроме, например: try: print("files and databases and stuff") except KeyboardInterrupt: print("clean-up")? - person Daedalus Mythos; 15.10.2014
comment
@DaedalusMythos Я никогда не предпочитаю длинные методы или длинные блоки (пусть это будет блок try-except). Вместо этого поместите все мясо вашей основной функции в другую функцию (например: safe_main()), и в вашей основной функции у вас будет только простая структура try-except, которая имеет только вызов функции в своем блоке try. Аналогичным образом вы можете упростить блок exclude. Например, в C++ стандартная библиотека вызывает вашу основную функцию аналогичным образом, заключенную в блок try-catch. - person pasztorpisti; 15.10.2014
comment
@DaedalusMythos Ну, я только что заметил, что у вас уже есть в основном 2 основных блока ... Вы можете легко сделать попытку-за исключением блока if __name__ == "__main__":. Тогда ваш блок try содержит только вызов main(). Смотрите мой обновленный ответ. - person pasztorpisti; 15.10.2014
comment
Спасибо за обновления! Я рассматривал возможность реализации try/except, как вы продемонстрировали, но я не знаю, как получить доступ к файлам/соединениям с базой данных из main() в cleanup().. - person Daedalus Mythos; 15.10.2014
comment
@DaedalusMythos Конечно, у вас должна быть общая переменная/состояние между main() и cleanup(). Вы должны знать, что прерывание может произойти в любое время, поэтому cleanup() должен быть готов к обработке любых возможных состояний вашего процесса. Однако это может быть так же просто, как закрыть несколько файлов или сбросить некоторые очереди. Это зависит от дизайна вашей программы. - person pasztorpisti; 15.10.2014
comment
Я никогда не реализовывал что-то вроде доли переменных между функциями. Не могли бы вы дать мне подсказку о том, как найти информацию, необходимую для выполнения работы? - person Daedalus Mythos; 15.10.2014

Я предлагаю использовать библиотеку signal для обработки сигналов. Сигналы не являются исключением и являются частью инфраструктуры Inter Process Communication (IPC) операционной системы. Система.

Сигналы могут помочь вам взаимодействовать с вашей программой, например, перезагрузить файл конфигурации, закрыть обработчик файла журнала во время ротации журнала и так далее. Большая часть процесса демона, такого как apache, дозирует его.

В сценариях оболочки есть команда trap для обработки сигналов и выполнения соответствующих действий на основе полученных сигналов.

Обычно python автоматически закрывает все обработчики файлов и соединение с базой данных во время выхода. Но на всякий случай у нас может быть функция для неявной обработки.

Код ниже перехватывает SIGINT и правильно закрывает файлы.

import signal
import sys

die = False

def handler(signum, frame):
    global die
    print('Got SIGINT.')
    die = True

def closeFile(fh):
    fh.flush()
    fh.close()

signal.signal(signal.SIGINT, handler)

fh = open('/tmp/a.txt', 'w')

while True:
    data = input('> ')

    if data == 'q':
        closeFile(fh)
        break
    else:
        fh.write(data + '\n')

    if die:
        closeFile(fh)
        print('Completed cleanup.. ')
        sys.exit()
person Kannan Mohan    schedule 15.10.2014
comment
Спасибо за твой ответ! Как я могу передать соединение с базой данных/открытый файл обработчику, чтобы закрыть его при вызове? - person Daedalus Mythos; 15.10.2014
comment
В python вы не можете обрабатывать сигналы, как в программе C. Когда ваш обработчик сигнала вызывается, обработчик сигнала C уже вернулся. Дополнительная информация: docs.python.org/2/library/signal.html Обрабатывая SIGINT напрямую, вы можете рискнуть, что ваша программа Python будет прервана в середине операции, которая в противном случае рассматривается как атомарная, когда дело доходит до обработки исключений. Вы можете безопасно вызывать методы Python из обработчика исключений, но вы не можете сделать то же самое из обработчика сигналов. Если нет сигналов, между атомарными операциями генерируются исключения. - person pasztorpisti; 15.10.2014
comment
@DaedalusMythos Я добавил пример кода, который закрывает обработчики файлов при получении SIGINT и перед завершением. - person Kannan Mohan; 17.10.2014