Как правильно завершить цикл при нажатии CTRL+C в Python
Я довольно новичок в Python и застрял со следующей проблемой. У меня есть скрипт, который обрабатывает файлы по одному и записывает вывод в отдельные файлы в соответствии с именем входного файла. Иногда мне нужно сломать скрипт, но я бы хотел, чтобы он закончил обработку текущего файла, а затем завершил работу (чтобы избежать файлов результатов с неполной информацией). Как кодировать это поведение в Python?
Вот что я попробовал.
а) Попробуйте - кроме блока
x = 1
print "Script started."
while True:
try:
print "Processing file #",x,"started...",
# do something time-cosnuming
time.sleep(1)
x += 1
print " finished."
except KeyboardInterrupt:
print "Bye"
print "x=",x
sys.exit()
sys.exit()
Выход:
Script started.
Processing file # 1 started... finished.
Processing file # 2 started... finished.
Processing file # 3 started... Bye
x= 3
Итерация № 3 не закончена изящно.
б) sys.excepthook
OriginalExceptHook = sys.excepthook
def NewExceptHook(type, value, traceback):
global Terminator
Terminator = True
if type == KeyboardInterrupt:
#exit("\nExiting by CTRL+C.") # this line was here originally
print("\n\nExiting by CTRL+C.\n\n")
else:
OriginalExceptHook(type, value, traceback)
sys.excepthook = NewExceptHook
global Terminator
Terminator = False
x = 1
while True:
print "Processing file #",x,"started...",
# do something time-cosnuming
time.sleep(1)
x += 1
print " finished."
if Terminator:
print "I'll be back!"
break
print "Bye"
print "x=",x
sys.exit()
Выход:
Script started.
Processing file # 1 started... finished.
Processing file # 2 started... finished.
Processing file # 3 started...
Exiting by CTRL+C.
Итерация № 3 не закончена изящно.
UPD # 1
@mguijarr, я немного изменил код так:
import time, sys
x = 1
print "Script started."
stored_exception=None
while True:
try:
print "Processing file #",x,"started...",
# do something time-cosnuming
time.sleep(1)
print "Processing file #",x,"part two...",
time.sleep(1)
print " finished."
if stored_exception:
break
x += 1
except KeyboardInterrupt:
print "[CTRL+C detected]",
stored_exception=sys.exc_info()
print "Bye"
print "x=",x
if stored_exception:
raise stored_exception[0], stored_exception[1], stored_exception[2]
sys.exit()
Вывод (протестирован с использованием "Python 2.7.6:: Anaconda 2.0.0 (64-bit)" на Win7-64bit):
Script started.
Processing file # 1 started... Processing file # 1 part two... finished.
Processing file # 2 started... Processing file # 2 part two... finished.
Processing file # 3 started... [CTRL+C detected] Processing file # 3 started... Processing file # 3 part two... finished.
Bye
x= 3
Traceback (most recent call last):
File "test2.py", line 12, in <module>
time.sleep(1)
KeyboardInterrupt
В этом случае итерация № 3 была эффективно перезапущена, что выглядит странно и не является желательным поведением. Можно ли этого избежать?
Я удалил запятые в выражениях "print" и добавил еще кое-что, чтобы увидеть, что итерация действительно перезапущена:
import time, sys
x = 1
y = 0
print "Script started."
stored_exception=None
while True:
try:
y=x*1000
y+=1
print "Processing file #",x,y,"started..."
y+=1
# do something time-cosnuming
y+=1
time.sleep(1)
y+=1
print "Processing file #",x,y,"part two..."
y+=1
time.sleep(1)
y+=1
print " finished.",x,y
y+=1
if stored_exception:
break
y+=1
x += 1
y+=1
except KeyboardInterrupt:
print "[CTRL+C detected]",
stored_exception=sys.exc_info()
print "Bye"
print "x=",x
print "y=",y
if stored_exception:
raise stored_exception[0], stored_exception[1], stored_exception[2]
sys.exit()
и вывод:
Script started.
Processing file # 1 1001 started...
Processing file # 1 1004 part two...
finished. 1 1006
Processing file # 2 2001 started...
Processing file # 2 2004 part two...
[CTRL+C detected] Processing file # 2 2001 started...
Processing file # 2 2004 part two...
finished. 2 2006
Bye
x= 2
y= 2007
Traceback (most recent call last):
File "test2.py", line 20, in <module>
time.sleep(1)
KeyboardInterrupt
4 ответа
Я бы просто использовал обработчик исключений, который бы ловил KeyboardInterrupt
и сохранить исключение. Затем, в тот момент, когда итерация закончена, если исключение находится в состоянии ожидания, я бы прервал цикл и повторно поднял исключение (чтобы позволить нормальной обработке исключения случиться).
Это работает (протестировано с Python 2.7):
x = 1
print "Script started."
stored_exception=None
while True:
try:
print "Processing file #",x,"started...",
# do something time-cosnuming
time.sleep(1)
print " finished."
if stored_exception:
break
x += 1
except KeyboardInterrupt:
stored_exception=sys.exc_info()
print "Bye"
print "x=",x
if stored_exception:
raise stored_exception[0], stored_exception[1], stored_exception[2]
sys.exit()
РЕДАКТИРОВАТЬ: как это было замечено в комментариях, этот ответ не удовлетворяет для оригинального плаката, вот решение, основанное на темах:
import time
import sys
import threading
print "Script started."
class MyProcessingThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
print "Processing file #",x,"started...",
# do something time-cosnuming
time.sleep(1)
print " finished."
for x in range(1,4):
task = MyProcessingThread()
task.start()
try:
task.join()
except KeyboardInterrupt:
break
print "Bye"
print "x=",x
sys.exit()
Я считаю, что создание класса с состоянием, которое обрабатывает пользовательские исключения, немного более элегантно, поскольку мне не нужно возиться с глобальными переменными, которые не работают в разных модулях.
import signal
import time
class GracefulExiter():
def __init__(self):
self.state = False
signal.signal(signal.SIGINT, self.change_state)
def change_state(self, signum, frame):
print("exit flag set to True (repeat to exit now)")
signal.signal(signal.SIGINT, signal.SIG_DFL)
self.state = True
def exit(self):
return self.state
x = 1
flag = GracefulExiter()
while True:
print("Processing file #",x,"started...")
time.sleep(1)
x+=1
print(" finished.")
if flag.exit():
break
Вы можете написать функцию обработки сигналов
import signal,sys,time
terminate = False
def signal_handling(signum,frame):
global terminate
terminate = True
signal.signal(signal.SIGINT,signal_handling)
x=1
while True:
print "Processing file #",x,"started..."
time.sleep(1)
x+=1
if terminate:
print "I'll be back"
break
print "bye"
print x
нажатие Ctrl+c отправляет прерывание SIGINT, которое выдает:
Processing file # 1 started...
Processing file # 2 started...
^CI'll be back
bye
3
Поздно на вечеринку, но это было мое решение:
#!/usr/bin/env python
# -*- coding: utf8 -*-
import sys
import signal
import time
interrupted = False
def main():
global interrupted
oldHandler = signal.signal(signal.SIGINT, handle_interrupt)
x = 1
print "Script started"
while True:
print "Processing file #",x,"started...",
# do something time-cosnuming
time.sleep(1)
x += 1
print " finished."
if interrupted:
break
signal.signal(signal.SIGINT, oldHandler)
def handle_interrupt(sig, frame):
global interrupted
interrupted = True
if __name__ == '__main__':
sys.exit(main())