回答:
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)
た量だけ実行されます。join(10)を使用して10個のプロセスのブロックI / Oがある場合、それらがすべてのプロセスが開始されるのを最大10個待つように設定しました。この例のようにデーモンフラグを使用します。stackoverflow.com/a/27420072/2480481。もちろん、uはフラグ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
2番目の関数呼び出しは終了せず、代わりにプロセスがトレースバックで終了します。
KeyboardInterrupt
常にスリープ状態のスレッドを停止するわけではありませんWindows上のPython 2では、スリープはキーボード割り込みによって常に割り込みされるわけではないことに注意してください。例:
@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が無視されるのをて
いずれにしても、スレッドを1秒以上スリープ状態にすることは避けます。これはプロセッサ時間の時代です。
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 {}
ます。タプルは変更できないため、Argsは問題ありません。
ユニットテストでタイムアウトコールを検索しているときに、このスレッドに遭遇しました。私は回答やサードパーティのパッケージに簡単なものを見つけられなかったので、以下のデコレータを記述して、コードに直接ドロップできます。
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内でtry / catch を実行pool.close()
し、catchの後に実行することで、その後も常にスレッドが必ず終了するようにすることができます。その後、投げ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を発生させます。
Python 3.2以降にネイティブで、2.7(pipインストール先物)にバックポートされています。
スレッドとプロセスの切り替えは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()とsignals()があるためですが、Windowsでは状況が少し異なります。Linuxのサブスレッドに関しては、シグナルを使用することはできません。
Windowsでプロセスを生成するには、プロセスを選択可能にする必要があります。装飾された関数やクラスメソッドの多くは選択できません。
したがって、dillやマルチプロセス(ピクルやマルチプロセシングではない)などのより優れたピッカーを使用する必要があります。そのため、ProcessPoolExecutorを使用できません(または機能が制限されている場合のみ)。
タイムアウト自体について-タイムアウトの意味を定義する必要があります-Windowsでは、プロセスを生成するのにかなりの(そして決定できない)時間を要するためです。これは、短いタイムアウトでは注意が必要です。プロセスの起動には約0.5秒かかります(簡単に!!!)。0.2秒のタイムアウトを指定するとどうなりますか?関数は0.5 + 0.2秒後にタイムアウトする必要がありますか(メソッドを0.2秒実行します)?または、呼び出されたプロセスは0.2秒後にタイムアウトする必要がありますか(その場合、装飾された関数は常にタイムアウトになります。
また、ネストされたデコレータは厄介な場合があり、サブスレッドでシグナルを使用することはできません。本当に普遍的なクロスプラットフォームのデコレーターを作成したい場合は、これらすべてを考慮に入れる(そしてテストする)必要があります。
その他の問題は、例外を呼び出し元に返しているだけでなく、ロギングの問題です(装飾された関数で使用されている場合-別のプロセスでのファイルへのロギングはサポートされていません)。
私はすべてのエッジケースをカバーしようとしました。パッケージwrapt_timeout_decoratorを調べるか、少なくともそこで使用されているユニットテストに触発された独自のソリューションをテストしてください。
@Alexis Eggermont-残念ながら、コメントするのに十分なポイントがありません-多分誰かがあなたに通知することができます-私はあなたのインポートの問題を解決したと思います。
timeout-decorator
Windowsシステムでは動作しません。Windowsはサポートしていません signal
てた。
Windowsシステムでtimeout-decoratorを使用すると、次のようになります。
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)
入れ子にする必要があった time.sleep(スレッドベースのアプローチではできない)によってブロックされない時限割り込み(SIGALARMではできない)が。私はここからコードをコピーして軽く変更しました: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()
GILを取得する特定のPython関数内で呼び出されるかのように、安全ではありません。たとえば、関数内で呼び出された場合、次のコードは決して、または非常に長い間戻りませんeval(2**9999999999**9999999999)
。stackoverflow.com/questions/22138190/…を