Я вызываю функцию на Python, которая, как я знаю, может зависнуть и вынудить меня перезапустить скрипт.
Как мне вызвать функцию или во что мне ее обернуть, чтобы, если это займет больше 5 секунд, скрипт отменял ее и делал что-то еще?
Переведено автоматически
Ответ 1
Вы можете использовать пакет signal, если вы работаете в UNIX:
In [1]: import signal
# Register an handler for the timeout In [2]: defhandler(signum, frame): ...: print("Forever is over!") ...: raise Exception("end of time") ...:
# This function *may* run for an indetermined time... In [3]: defloop_forever(): ...: import time ...: while1: ...: print("sec") ...: time.sleep(1) ...: ...:
# Register the signal function handler In [4]: signal.signal(signal.SIGALRM, handler) Out[4]: 0
# Define a timeout for your function In [5]: signal.alarm(10) Out[5]: 0
In [6]: try: ...: loop_forever() ...: except Exception, exc: ...: print(exc) ....: sec sec sec sec sec sec sec sec Forever is over! end of time
# Cancel the timer if the function returned before timeout # (ok, mine won't but yours maybe will :) In [7]: signal.alarm(0) Out[7]: 0
Через 10 секунд после вызова signal.alarm(10) вызывается обработчик. При этом возникает исключение, которое можно перехватить из обычного кода Python.
Этот модуль плохо работает с потоками (но тогда, кто это делает?)
Обратите внимание, что поскольку мы создаем исключение при возникновении тайм-аута, оно может в конечном итоге быть перехвачено и проигнорировано внутри функции, например, одной из таких функций:
Вы можете использовать multiprocessing.Process именно для этого.
Код
import multiprocessing import time
# bar defbar(): for i inrange(100): print"Tick" time.sleep(1)
if __name__ == '__main__': # Start bar as a process p = multiprocessing.Process(target=bar) p.start()
# Wait for 10 seconds or until process finishes p.join(10)
# If thread is still active if p.is_alive(): print"running... let's kill it..."
# Terminate - may not work if process is stuck for good p.terminate() # OR Kill - will work for sure, no chance for process to finish nicely however # p.kill()
p.join()
Ответ 3
Как мне вызвать функцию или во что мне ее обернуть, чтобы, если это заняло больше 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
Теперь мы импортировали нашу функциональность из стандартной библиотеки.
exit_after декоратор
Далее нам понадобится функция для завершения main() из дочернего потока:
defquit_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
А вот и сам декоратор:
defexit_after(s): ''' use as decorator to exit process if function takes longer than s seconds ''' defouter(fn): definner(*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) defcountdown(n): print('countdown started', flush=True) for i inrange(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, например:
>>> 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
Я бы в любом случае не стал переводить поток в режим ожидания более секунды - это целая вечность процессорного времени.
Как мне вызвать функцию или во что мне ее обернуть, чтобы, если это займет больше 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
Ответ 4
У меня есть другое предложение, которое представляет собой чистую функцию (с тем же API, что и предложение о потоковой передаче) и, кажется, работает нормально (на основе предложений в этом потоке)
deftimeout(func, args=(), kwargs={}, timeout_duration=1, default=None): import signal
classTimeoutError(Exception): pass
defhandler(signum, frame): raise TimeoutError()
# set the timeout handler signal.signal(signal.SIGALRM, handler) signal.alarm(timeout_duration) try: result = func(*args, **kwargs) except TimeoutError as exc: result = default finally: signal.alarm(0)