Как мне вызвать функцию или как ее обернуть, чтобы, если это заняло более 5 секунд, скрипт ее отменил?
Я опубликовал гист , который решает этот вопрос / проблему с декоратором и threading.Timer
. Вот это с разбивкой.
Импорт и настройки совместимости
Он был протестирован на Python 2 и 3. Он также должен работать в Unix / Linux и Windows.
Сначала импорт. Эти попытки сохранить код непротиворечивым независимо от версии Python:
from __future__ import print_function
import sys
import threading
from time import sleep
try:
import thread
except ImportError:
import _thread as thread
Использовать независимый от версии код:
try:
range, _print = xrange, print
def print(*args, **kwargs):
flush = kwargs.pop('flush', False)
_print(*args, **kwargs)
if flush:
kwargs.get('file', sys.stdout).flush()
except NameError:
pass
Теперь мы импортировали нашу функциональность из стандартной библиотеки.
exit_after
декоратор
Далее нам нужна функция для завершения main()
из дочернего потока:
def quit_function(fn_name):
# print to stderr, unbuffered in Python 2.
print('{0} took too long'.format(fn_name), file=sys.stderr)
sys.stderr.flush() # Python 3 stderr is likely buffered.
thread.interrupt_main() # raises KeyboardInterrupt
А вот и сам декоратор:
def exit_after(s):
'''
use as decorator to exit process if
function takes longer than s seconds
'''
def outer(fn):
def inner(*args, **kwargs):
timer = threading.Timer(s, quit_function, args=[fn.__name__])
timer.start()
try:
result = fn(*args, **kwargs)
finally:
timer.cancel()
return result
return inner
return outer
Использование
А вот использование, которое прямо отвечает на ваш вопрос о выходе через 5 секунд !:
@exit_after(5)
def countdown(n):
print('countdown started', flush=True)
for i in range(n, -1, -1):
print(i, end=', ', flush=True)
sleep(1)
print('countdown finished')
Демо-версия:
>>> countdown(3)
countdown started
3, 2, 1, 0, countdown finished
>>> countdown(10)
countdown started
10, 9, 8, 7, 6, countdown took too long
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 11, in inner
File "<stdin>", line 6, in countdown
KeyboardInterrupt
Второй вызов функции не завершится, вместо этого процесс должен завершиться с отслеживанием!
KeyboardInterrupt
не всегда останавливает спящий поток
Обратите внимание, что сон не всегда прерывается прерыванием клавиатуры, в Python 2 в Windows, например ::1010 *
@exit_after(1)
def sleep10():
sleep(10)
print('slept 10 seconds')
>>> sleep10()
sleep10 took too long # Note that it hangs here about 9 more seconds
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 11, in inner
File "<stdin>", line 3, in sleep10
KeyboardInterrupt
и при этом он вряд ли прервет выполнение кода в расширениях, если он явно не проверяет PyErr_CheckSignals()
, см. Cython, Python и KeyboardInterrupt игнорируются
Во всяком случае, я бы не спал нить больше секунды - это целая вечность процессора.
Как мне вызвать функцию или как ее обернуть, чтобы, если это займет более 5 секунд, скрипт отменит ее и сделает что-то еще?
Чтобы поймать это и сделать что-то еще, вы можете поймать KeyboardInterrupt.
>>> try:
... countdown(10)
... except KeyboardInterrupt:
... print('do something else')
...
countdown started
10, 9, 8, 7, 6, countdown took too long
do something else