Відповіді:
Ви можете використовувати пакет сигналів, якщо ви працюєте в UNIX:
In [1]: import signal
# Register an handler for the timeout
In [2]: def handler(signum, frame):
...: print("Forever is over!")
...: raise Exception("end of time")
...:
# This function *may* run for an indetermined time...
In [3]: def loop_forever():
...: import time
...: while 1:
...: 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 секунд після дзвінка викликається alarm.alarm(10)
обробник. Це створює виняток, який ви можете перехопити із звичайного коду Python.
Цей модуль не грає добре з потоками (але тоді, хто це робить?)
Зауважте, що оскільки ми створюємо виняток, коли виникає час очікування, він може виявитись і потрапив до функції, наприклад однієї такої функції:
def loop_forever():
while 1:
print('sec')
try:
time.sleep(10)
except:
continue
signal.alarm
і пов'язані з ними SIGALRM
недоступні на платформах Windows.
signal.signal
--- чи всі вони працюватимуть належним чином? Чи не буде кожен signal.signal
виклик скасовувати "одночасний"?
Ви можете використовувати multiprocessing.Process
саме це.
Код
import multiprocessing
import time
# bar
def bar():
for i in range(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
p.terminate()
p.join()
join()
. що робить вашу кількість x одночасних підпроцесів запущеними, поки вони не закінчать свою роботу, або кількість, визначена в join(10)
. Якщо у вас є блокуючий введення / виведення для 10 процесів, використовуючи приєднання (10), ви встановили їх чекати всіх максимум 10 для запущеного процесу EACH. Використовуйте прапор демона, як у цьому прикладі stackoverflow.com/a/27420072/2480481 . Звичайно, ви можете передати прапор daemon=True
безпосередньо у multiprocessing.Process()
функцію.
terminate() ... Note that exit handlers and finally clauses, etc., will not be executed. Note that descendant processes of the process will not be terminated – they will simply become orphaned.
Як мені зателефонувати у функцію або в що я її завертую, якщо сценарій займе більше 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, наприклад:
@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
thread.interrupt_main()
, чому я не можу безпосередньо підняти виняток?
multiprocessing.connection.Client
з цим? - Намагаючись вирішити: stackoverflow.com/questions/57817955 / ...
У мене є інша пропозиція, яка є чистою функцією (з тим самим API, що і пропозиція про нарізку) і, здається, працює добре (на основі пропозицій з цієї теми)
def timeout(func, args=(), kwargs={}, timeout_duration=1, default=None):
import signal
class TimeoutError(Exception):
pass
def handler(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)
return result
timeout
. Набагато краще встановити за замовчуванням значення None
та, у першому рядку функції, додати kwargs = kwargs or {}
. Арги добре, оскільки кортежі не змінюються.
Я натрапив на цю нитку під час пошуку виклику в режимі очікування на тестових одиницях. Я не знайшов нічого простого у відповідях або сторонніх пакунках, тому я написав декоратор нижче, що ви можете перейти прямо до коду:
import multiprocessing.pool
import functools
def timeout(max_timeout):
"""Timeout decorator, parameter in seconds."""
def timeout_decorator(item):
"""Wrap the original function."""
@functools.wraps(item)
def func_wrapper(*args, **kwargs):
"""Closure for function."""
pool = multiprocessing.pool.ThreadPool(processes=1)
async_result = pool.apply_async(item, args, kwargs)
# raises a TimeoutError if execution exceeds max_timeout
return async_result.get(max_timeout)
return func_wrapper
return timeout_decorator
Тоді настільки ж просто, як провести тайм-аут тесту чи будь-якої функції, яка вам подобається:
@timeout(5.0) # if execution takes longer than 5 seconds, raise a TimeoutError
def test_base_regression(self):
...
Exception
всередині func_wrapper і зробити pool.close()
після цього, щоб забезпечити, щоб нитка завжди відмирала після цього, незважаючи ні на що. Тоді ви можете кинути TimeoutError
або що завгодно після цього. Здається, працює для мене.
RuntimeError: can't start new thread
. Чи все-таки спрацює, якщо я проігнорую це чи щось інше я можу зробити, щоб обійти це? Спасибі заздалегідь!
stopit
Пакет, знайдений на PyPI, здається обробляти таймаут добре.
Мені подобається @stopit.threading_timeoutable
декоратор, який додає timeout
параметр до декорованої функції, який робить те, що ви очікуєте, він зупиняє функцію.
Перевірте це на pypi: https://pypi.python.org/pypi/stopit
Є багато пропозицій, але жодна з використанням concurrent.futures, що, на мою думку, є найбільш розбірливим способом впоратися з цим.
from concurrent.futures import ProcessPoolExecutor
# Warning: this does not terminate function if timeout
def timeout_five(fnc, *args, **kwargs):
with ProcessPoolExecutor() as p:
f = p.submit(fnc, *args, **kwargs)
return f.result(timeout=5)
Супер просте для читання та обслуговування.
Ми робимо пул, подаємо єдиний процес, а потім чекаємо до 5 секунд, перш ніж підняти TimeoutError, який ви могли б зловити та обробити, як би вам це було потрібно.
Native для python 3.2+ та підтримується до 2.7 (ф'ючерси на встановлення піп).
Переключення між потоками та процесами настільки ж просто, як і заміна ProcessPoolExecutor
на ThreadPoolExecutor
.
Якщо ви хочете припинити процес вчасно, я б запропонував заглянути в Pebble .
Чудовий, простий у використанні та надійний тайм-аут-декоратор проекту PyPi ( https://pypi.org/project/timeout-decorator/ )
установка :
pip install timeout-decorator
Використання :
import time
import timeout_decorator
@timeout_decorator.timeout(5)
def mytest():
print "Start"
for i in range(1,10):
time.sleep(1)
print "%d seconds have passed" % i
if __name__ == '__main__':
mytest()
Я автор wrapt_timeout_decorator
Більшість представлених тут рішень з першим поглядом працюють підвісно під Linux - оскільки у нас є fork () та сигнали (), але у Windows все виглядає дещо інакше. А якщо мова йде про підрядки в Linux, ви більше не можете використовувати Signals.
Для нерестування процесу під Windows він повинен бути доступним - і багато прикрашених функцій або методів Class - ні.
Тож вам потрібно використовувати кращий підбирач, як кріп та багатопроцесорний процес (а не маринований і багатопроцесовий) - ось чому ви не можете використовувати ProcessPoolExecutor (або лише з обмеженою функціональністю).
Що стосується самого тайм-ауту - вам потрібно визначити, що означає час очікування - адже для Windows знадобиться чималий (і не визначений) час для нерестування процесу. Це може бути складним у коротких тайм-аутах. Припустимо, процес нересту займає близько 0,5 секунд (легко !!!). Якщо ви даєте тайм-аут 0,2 секунди, що має статися? Чи повинен час вимкнення функції через 0,5 + 0,2 секунди (нехай метод працює 0,2 секунди)? Або повинен затримати час виклику процесу через 0,2 секунди (у такому випадку декорована функція ВИНАГО буде таймаутом, оскільки в цей час вона навіть не породжується)?
Також вкладені декоратори можуть бути неприємними, і Ви не можете використовувати Сигнали в підрядці. Якщо ви хочете створити по-справжньому універсальний, кросплатформенний декоратор, все це потрібно врахувати (і протестувати).
Інші проблеми - це виняток виклику назад абоненту, а також проблеми ведення журналу (якщо використовується в оформленій функції - журнал до файлів в іншому процесі НЕ підтримується)
Я намагався висвітлити всі крайові випадки. Ви можете заглянути в пакет wrapt_timeout_decorator або хоча б протестувати власні рішення, натхнені одиничними тестами, які використовуються там.
@ Алексис Еггермонт - на жаль, у мене немає достатньої кількості балів для коментарів - можливо, хтось інший може повідомити вас - я думаю, що я вирішив вашу проблему з імпортом.
timeout-decorator
не працюють в системі Windows, тому що Windows не підтримує signal
добре.
Якщо ви використовуєте timeout-decorator в системі Windows, ви отримаєте наступне
AttributeError: module 'signal' has no attribute 'SIGALRM'
Деякі пропонували використовувати, use_signals=False
але не спрацювали.
Автор @bitranox створив такий пакет:
pip install https://github.com/bitranox/wrapt-timeout-decorator/archive/master.zip
Зразок коду:
import time
from wrapt_timeout_decorator import *
@timeout(5)
def mytest(message):
print(message)
for i in range(1,10):
time.sleep(1)
print('{} seconds have passed'.format(i))
def main():
mytest('starting')
if __name__ == '__main__':
main()
Надає наступне виняток:
TimeoutError: Function mytest timed out after 5 seconds
from wrapt_timeout_decorator import *
схоже, вбиває частину мого іншого імпорту. Наприклад, я отримую ModuleNotFoundError: No module named 'google.appengine'
, але я не отримую цієї помилки, якщо не імпортую wrapt_timeout_decorator
Ми можемо використовувати сигнали для того ж. Я думаю, що наведений нижче приклад буде корисним для вас. Це дуже просто в порівнянні з нитками.
import signal
def timeout(signum, frame):
raise myException
#this is an infinite loop, never ending under normal circumstances
def main():
print 'Starting Main ',
while 1:
print 'in main ',
#SIGALRM is only usable on a unix platform
signal.signal(signal.SIGALRM, timeout)
#change 5 to however many seconds you need
signal.alarm(5)
try:
main()
except myException:
print "whoops"
try: ... except: ...
завжди погана ідея.
#!/usr/bin/python2
import sys, subprocess, threading
proc = subprocess.Popen(sys.argv[2:])
timer = threading.Timer(float(sys.argv[1]), proc.terminate)
timer.start()
proc.wait()
timer.cancel()
exit(proc.returncode)
У мене була потреба у нестабільних тимчасових перериваннях (які SIGALARM не може зробити), які не заблокуються time.sleep (що не може зробити потоковий підхід). Я закінчив копіювати та злегка змінювати код звідси: http://code.activestate.com/recipes/577600-queue-for-managing-multiple-sigalrm-alarms-concurr/
Сам код:
#!/usr/bin/python
# lightly modified version of http://code.activestate.com/recipes/577600-queue-for-managing-multiple-sigalrm-alarms-concurr/
"""alarm.py: Permits multiple SIGALRM events to be queued.
Uses a `heapq` to store the objects to be called when an alarm signal is
raised, so that the next alarm is always at the top of the heap.
"""
import heapq
import signal
from time import time
__version__ = '$Revision: 2539 $'.split()[1]
alarmlist = []
__new_alarm = lambda t, f, a, k: (t + time(), f, a, k)
__next_alarm = lambda: int(round(alarmlist[0][0] - time())) if alarmlist else None
__set_alarm = lambda: signal.alarm(max(__next_alarm(), 1))
class TimeoutError(Exception):
def __init__(self, message, id_=None):
self.message = message
self.id_ = id_
class Timeout:
''' id_ allows for nested timeouts. '''
def __init__(self, id_=None, seconds=1, error_message='Timeout'):
self.seconds = seconds
self.error_message = error_message
self.id_ = id_
def handle_timeout(self):
raise TimeoutError(self.error_message, self.id_)
def __enter__(self):
self.this_alarm = alarm(self.seconds, self.handle_timeout)
def __exit__(self, type, value, traceback):
try:
cancel(self.this_alarm)
except ValueError:
pass
def __clear_alarm():
"""Clear an existing alarm.
If the alarm signal was set to a callable other than our own, queue the
previous alarm settings.
"""
oldsec = signal.alarm(0)
oldfunc = signal.signal(signal.SIGALRM, __alarm_handler)
if oldsec > 0 and oldfunc != __alarm_handler:
heapq.heappush(alarmlist, (__new_alarm(oldsec, oldfunc, [], {})))
def __alarm_handler(*zargs):
"""Handle an alarm by calling any due heap entries and resetting the alarm.
Note that multiple heap entries might get called, especially if calling an
entry takes a lot of time.
"""
try:
nextt = __next_alarm()
while nextt is not None and nextt <= 0:
(tm, func, args, keys) = heapq.heappop(alarmlist)
func(*args, **keys)
nextt = __next_alarm()
finally:
if alarmlist: __set_alarm()
def alarm(sec, func, *args, **keys):
"""Set an alarm.
When the alarm is raised in `sec` seconds, the handler will call `func`,
passing `args` and `keys`. Return the heap entry (which is just a big
tuple), so that it can be cancelled by calling `cancel()`.
"""
__clear_alarm()
try:
newalarm = __new_alarm(sec, func, args, keys)
heapq.heappush(alarmlist, newalarm)
return newalarm
finally:
__set_alarm()
def cancel(alarm):
"""Cancel an alarm by passing the heap entry returned by `alarm()`.
It is an error to try to cancel an alarm which has already occurred.
"""
__clear_alarm()
try:
alarmlist.remove(alarm)
heapq.heapify(alarmlist)
finally:
if alarmlist: __set_alarm()
і приклад використання:
import alarm
from time import sleep
try:
with alarm.Timeout(id_='a', seconds=5):
try:
with alarm.Timeout(id_='b', seconds=2):
sleep(3)
except alarm.TimeoutError as e:
print 'raised', e.id_
sleep(30)
except alarm.TimeoutError as e:
print 'raised', e.id_
else:
print 'nope.'
Ось невелике вдосконалення даного рішення на основі потоку.
Код нижче підтримує винятки :
def runFunctionCatchExceptions(func, *args, **kwargs):
try:
result = func(*args, **kwargs)
except Exception, message:
return ["exception", message]
return ["RESULT", result]
def runFunctionWithTimeout(func, args=(), kwargs={}, timeout_duration=10, default=None):
import threading
class InterruptableThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.result = default
def run(self):
self.result = runFunctionCatchExceptions(func, *args, **kwargs)
it = InterruptableThread()
it.start()
it.join(timeout_duration)
if it.isAlive():
return default
if it.result[0] == "exception":
raise it.result[1]
return it.result[1]
Викликаєте його з 5-секундним тайм-аутом:
result = timeout(remote_calculate, (myarg,), timeout_duration=5)
runFunctionCatchExceptions()
певних функцій Python отримання GIL викликається. Наприклад , наступне ніколи, або дуже довго, якщо повернення викликається в функції: eval(2**9999999999**9999999999)
. Див stackoverflow.com/questions/22138190 / ...